From 37b7e5a6274f5bbf6ee8de2f64af9a1ad0f0c66d Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 3 Oct 2016 14:45:07 +0200 Subject: [PATCH 01/77] Make GenCSuite more robust - augment list of known compilers (with compcert) - adapt this list for the current OS - use advanced parameters (e.g. sanitizers) - run all found compilers - add ability to disable a given compiler on a case-by-case basis - ignore files generated by tests - improve error messages - report warnings and other errors from the GenC phase (and others) --- .gitignore | 5 + .../genc/valid/ExpressionOrder.disabled | 1 + .../genc/valid/IntegralColor.disabled | 1 + src/test/scala/leon/genc/GenCSuite.scala | 200 +++++++++++++----- 4 files changed, 157 insertions(+), 50 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/ExpressionOrder.disabled create mode 100644 src/test/resources/regression/genc/valid/IntegralColor.disabled diff --git a/.gitignore b/.gitignore index 823f71028..4d71b1af6 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,11 @@ Leon.iml #scripts out-classes +#genc test suite +*.o +echo.txt +test.txt + #z3 .z3-trace diff --git a/src/test/resources/regression/genc/valid/ExpressionOrder.disabled b/src/test/resources/regression/genc/valid/ExpressionOrder.disabled new file mode 100644 index 000000000..7f65a6d93 --- /dev/null +++ b/src/test/resources/regression/genc/valid/ExpressionOrder.disabled @@ -0,0 +1 @@ +compcert diff --git a/src/test/resources/regression/genc/valid/IntegralColor.disabled b/src/test/resources/regression/genc/valid/IntegralColor.disabled new file mode 100644 index 000000000..7f65a6d93 --- /dev/null +++ b/src/test/resources/regression/genc/valid/IntegralColor.disabled @@ -0,0 +1 @@ +compcert diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala index 5aadd2758..0ceec31f1 100644 --- a/src/test/scala/leon/genc/GenCSuite.scala +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -5,10 +5,11 @@ package genc import leon.test.LeonRegressionSuite +import leon.DefaultReporter import leon.frontends.scalac.ExtractionPhase import leon.regression.verification.XLangVerificationSuite import leon.purescala.Definitions.Program -import leon.utils.{ PreprocessingPhase, UniqueCounter } +import leon.utils.{ InterruptManager, PreprocessingPhase, UniqueCounter } import scala.concurrent._ import ExecutionContext.Implicits.global @@ -20,11 +21,12 @@ import scala.io.Source import java.io.{ ByteArrayInputStream, File } import java.nio.file.{ Files, Path, Paths } +import scala.language.postfixOps + class GenCSuite extends LeonRegressionSuite { private val testDir = "regression/genc/" private lazy val tmpDir = Files.createTempDirectory("genc") - private val ccflags = "-std=c99 -g -O0" private val timeout = 60 // seconds, for processes execution private val counter = new UniqueCounter[Unit] @@ -32,11 +34,32 @@ class GenCSuite extends LeonRegressionSuite { private case class ExtendedContext( leon: LeonContext, - progName: String, // name of the source file, without extention - sourceDir: String, // directory in which the source lives - inputFileOpt: Option[String] // optional path to an file to be used as stdin at runtime + progName: String, // name of the source file, without extention + sourceDir: String, // directory in which the source lives + inputFileOpt: Option[String], // optional path to an file to be used as stdin at runtime + disabledCompilers: Seq[String] // List of compiler (title) that should not be used for this test ) + private case class Compiler(title: String, name: String, options: String*) { + lazy val flags = options mkString " " + + override def toString = title + } + + private object WarningOrAboveReporter extends DefaultReporter(Set()) { + override def emit(msg: Message): Unit = msg.severity match { + case this.WARNING | this.FATAL | this.ERROR | this.INTERNAL => + super.emit(msg) + case _ => + } + } + + override def createLeonContext(opts: String*): LeonContext = { + val reporter = WarningOrAboveReporter + Main.processOptions(opts.toList).copy(reporter = reporter, interruptManager = new InterruptManager(reporter)) + } + + // Tests are run as follows: // - before mkTest is run, all valid test are verified using XLangVerificationSuite // - The classic ExtractionPhase & PreprocessingPhase are run on all input files @@ -79,8 +102,13 @@ class GenCSuite extends LeonRegressionSuite { val inputName = filePrefix + ".input" val inputOpt = if (Files.isReadable(Paths.get(inputName))) Some(inputName) else None + val disabledCompilersName = filePrefix + ".disabled" + val disabledCompilers = + if (Files.isReadable(Paths.get(disabledCompilersName))) Source.fromFile(disabledCompilersName).getLines.toSeq + else Nil + val ctx = createLeonContext(s"--o=$tmpDir/$name.c") - val xCtx = ExtendedContext(ctx, name, sourceDir, inputOpt) + val xCtx = ExtendedContext(ctx, name, sourceDir, inputOpt, disabledCompilers) val displayName = s"$cat/$name.scala" val index = counter.nextGlobal @@ -98,10 +126,12 @@ class GenCSuite extends LeonRegressionSuite { } // Run a process with a timeout and return the status code - private def runProcess(pb: ProcessBuilder): Int = - runProcess(pb.run) + private def runProcess(pb: ProcessBuilder): Int = { + val logger = ProcessLogger(stdout => info(s"[from process] $stdout"), stderr => () /* screem silently */) + runProcessImpl(pb.run(logger)) + } - private def runProcess(p: Process): Int = { + private def runProcessImpl(p: Process): Int = { val f = Future(blocking(p.exitValue())) try { Await.result(f, duration.Duration(timeout, "sec")) @@ -112,31 +142,84 @@ class GenCSuite extends LeonRegressionSuite { } } - // Determine which C compiler is available - private def detectCompiler: Option[String] = { + // Determine which C compilers are available + private def detectCompilers: Seq[Compiler] = { + import org.apache.commons.lang3.SystemUtils._ + val testCode = "int main() { return 0; }" + val testSource = Files.createTempFile(tmpDir, "test", ".c") val testBinary = s"$tmpDir/test" + // Because not all compiler understand `-xc` we write the test code into a file. + Files.write(testSource, testCode.getBytes) + // NOTE this code might print error on stderr when a non-existing compiler // is used. It seems that even with a special ProcessLogger the RuntimeException // is printed for some reason. - def testCompiler(cc: String): Boolean = try { - def input = new ByteArrayInputStream(testCode.getBytes()) - val process = s"$cc $ccflags -o $testBinary -xc -" #< input #&& s"$testBinary" + def testCompiler(cc: Compiler): Boolean = { + info(s"Testing presence of ${cc.name}") + + val result = testCompilerImpl(cc) + + if (result) + info(s"${cc.name} is supported with parameters ${cc.flags}") + else + info(s"Failed to detect ${cc.name}") + + result + } + + def testCompilerImpl(cc: Compiler): Boolean = try { + val process = + (if (IS_OS_UNIX) s"which ${cc.name}" else s"where.exe ${cc.name}") #&& + s"${cc.name} ${cc.flags} -o $testBinary $testSource" #&& + s"$testBinary" #&& + s"${cc.name} --version" + runProcess(process) == 0 } catch { case _: java.lang.RuntimeException => false } - val knownCompiler = "cc" :: "clang" :: "gcc" :: "mingw" :: Nil + val std99 = "-std=c99" + val clangSanitizerAddress = "-fsanitize=address" + val clangSanitizerOthers = "-fsanitize=undefined,safe-stack" // address mode incompatible with those + val gccSanitizer = "-fsanitize=address,undefined" + + // For clang on OS X we make sure to use the one from brew instead of the one from Xcode + val brewClangBin = "/usr/local/opt/llvm/bin/clang" + val brewClangCompiler = "-I/usr/local/opt/llvm/include" + val brewClangLinker = "-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib" + val brewClang1 = Compiler("brew.clang.address", brewClangBin, std99, clangSanitizerAddress, brewClangCompiler, brewClangLinker) + val brewClang2 = Compiler("brew.clang.others", brewClangBin, std99, clangSanitizerOthers, brewClangCompiler, brewClangLinker) + + // Similarly for GCC, we avoid the default one on OS X + val brewGccBin = "gcc-6" // assuming it's still version 6 + val brewGcc = Compiler("brew.gcc-6", brewGccBin, std99, gccSanitizer) + + val clang1 = Compiler("clang.address", "clang", std99, clangSanitizerAddress) + val clang2 = Compiler("clang.other", "clang", std99, clangSanitizerOthers) + val gcc1 = Compiler("gcc", "gcc", std99) + val gcc2 = Compiler("gcc", "gcc", std99, gccSanitizer) + val mingw = Compiler("mingw", "mingw", std99) + + val compcert = Compiler("compcert", "ccomp", "-fno-unprototyped", "-fstruct-passing") + + val compilers: Seq[Compiler] = + if (IS_OS_MAC_OSX) brewClang1 :: brewClang2 :: brewGcc :: compcert :: Nil + else if (IS_OS_LINUX) clang1 :: clang2 :: gcc1 :: gcc2 :: compcert :: Nil + else if (IS_OS_WINDOWS) mingw :: compcert :: Nil + else fail(s"unknown operating system $OS_NAME") + // Note that VS is not in the list as we cannot specify C99 dialect - knownCompiler find testCompiler + compilers filter testCompiler } - private def convert(xCtx: ExtendedContext)(prog: Program) = { + private def convert(xCtx: ExtendedContext)(prog: Program): CAST.Prog = { try { + info(s"(CAST)${xCtx.progName}") GenerateCPhase(xCtx.leon, prog) } catch { case fe: LeonFatalError => @@ -148,37 +231,49 @@ class GenCSuite extends LeonRegressionSuite { CFileOutputPhase(xCtx.leon, cprog) } - private def compile(xCtx: ExtendedContext, cc: String)(unused: Unit) = { - val basename = s"$tmpDir/${xCtx.progName}" - val sourceFile = s"$basename.c" - val compiledProg = basename + // Check whether a regression test is disabled for a given compiler. + // NOTE this is useful to disable error when using VLA with compcert (which doesn't all them). + private def enabled(xCtx: ExtendedContext, cc: Compiler): Boolean = !(xCtx.disabledCompilers contains cc.title) + + // Return the list of compiled programs + private def compile(xCtx: ExtendedContext, compilers: Seq[Compiler])(unused: Unit): Seq[String] = for { cc <- compilers if enabled(xCtx, cc) } yield { + val basename = s"$tmpDir/${xCtx.progName}" + val sourceFile = s"$basename.c" + val bin = s"$basename.${cc.title}" - val process = Process(s"$cc $ccflags $sourceFile -o $compiledProg") + info(s"Compiling program for compiler ${cc.title}") + val cmd = s"${cc.name} ${cc.flags} $sourceFile -o $bin" + + val process = Process(cmd) val status = runProcess(process) - assert(status == 0, "Compilation of converted program failed") + if (status != 0) + fail(s"Compilation of converted program failed with ${cc.title}. Command was: $cmd") + + bin } // Evaluate the C program, making sure it succeeds, and return the file containing the output - private def evaluateC(xCtx: ExtendedContext): File = { - val compiledProg = s"$tmpDir/${xCtx.progName}" - + private def evaluateC(xCtx: ExtendedContext, bin: String): File = { // If available, use the given input file val baseCommand = xCtx.inputFileOpt match { - case Some(inputFile) => compiledProg #< new File(inputFile) - case None => Process(compiledProg) + case Some(inputFile) => bin #< new File(inputFile) + case None => Process(bin) } // Redirect output to a tmp file - val outputFile = new File(s"$compiledProg.c_output") + val outputFile = new File(s"$bin.c_output") val command = baseCommand #> outputFile - // println(s"Using input file for ${xCtx.progName}: ${xCtx.inputFileOpt.isDefined}") + // info(s"Using input file for ${xCtx.progName}: ${xCtx.inputFileOpt.isDefined}") + info(s"Evaluating binary ${bin split "/" last}") // TODO add a memory limit val status = runProcess(command) - assert(status == 0, s"Evaluation of converted program failed with status [$status]") + + if (status != 0) + fail(s"Evaluation of $bin failed with status [$status]") outputFile } @@ -202,44 +297,47 @@ class GenCSuite extends LeonRegressionSuite { case None => Process(runBase) } - // println(s"COMPILE: $compile") - // println(s"RUN: $run") + // info(s"COMPILE: $compile") + // info(s"RUN: $run") val outputFile = new File(s"$tmpDir/${xCtx.progName}.scala_output") val command = compile #&& (run #> outputFile) val status = runProcess(command) - assert(status == 0, s"Compilation or evaluation of the source program failed") + + if (status != 0) + fail(s"Compilation or evaluation of the source program failed") outputFile } // Evaluate both Scala and C programs, making sure their output matches - private def evaluate(xCtx: ExtendedContext)(unused: Unit) = { - val c = Source.fromFile(evaluateC(xCtx)).getLines + private def evaluate(xCtx: ExtendedContext)(binaries: Seq[String]) = { + val cOuts = binaries map { bin => Source.fromFile(evaluateC(xCtx, bin)).getLines } val scala = Source.fromFile(evaluateScala(xCtx)).getLines // Compare outputs - assert((c zip scala) forall { case (c, s) => c == s }, "Output mismatch") + for { (c, bin) <- cOuts zip binaries } { + info(s"Checking the result of ${bin split '/' last}") + assert((c zip scala) forall { case (c, s) => c == s }, s"Output mismatch for $bin") + } } private def forEachFileIn(cat: String)(block: (ExtendedContext, Program) => Unit) { val fs = filesInResourceDir(testDir + cat, _.endsWith(".scala")).toList - fs foreach { file => - assert(file.exists && file.isFile && file.canRead, - s"Benchmark ${file.getName} is not a readable file") - } + for { file <- fs } + assert(file.exists && file.isFile && file.canRead, s"Benchmark ${file.getName} is not a readable file") val files = fs map { _.getPath } mkTest(files, cat)(block) } - protected def testDirectory(cc: String, cat: String) = forEachFileIn(cat) { (xCtx, prog) => + protected def testDirectory(compilers: Seq[Compiler], cat: String) = forEachFileIn(cat) { (xCtx, prog) => val converter = convert(xCtx) _ val saver = saveToFile(xCtx) _ - val compiler = compile(xCtx, cc) _ + val compiler = compile(xCtx, compilers) _ val evaluator = evaluate(xCtx) _ val pipeline = converter andThen saver andThen compiler andThen evaluator @@ -247,8 +345,8 @@ class GenCSuite extends LeonRegressionSuite { pipeline(prog) } - protected def testValid(cc: String) = testDirectory(cc, "valid") - protected def testUnverified(cc: String) = testDirectory(cc, "unverified"); + protected def testValid(compilers: Seq[Compiler]) = testDirectory(compilers, "valid") + protected def testUnverified(compilers: Seq[Compiler]) = testDirectory(compilers, "unverified"); protected def testInvalid() = forEachFileIn("invalid") { (xCtx, prog) => intercept[LeonFatalError] { @@ -273,12 +371,14 @@ class GenCSuite extends LeonRegressionSuite { protected def testAll() = { // Set C compiler according to the platform we're currently running on - detectCompiler match { - case Some(cc) => - testValid(cc) - testUnverified(cc) - case None => + detectCompilers match { + case Nil => test("dectecting C compiler") { fail("no C compiler found") } + + case compilers => + info(s"Compiler(s) used: ${compilers mkString ", "}") + testValid(compilers) + testUnverified(compilers) } testInvalid() From 420b145ed879203285345b66c689b317be60ccfe Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 8 Nov 2016 17:24:54 +0100 Subject: [PATCH 02/77] Add many regression tests --- .../genc/invalid/AsInstanceOf.scala | 24 ++++ .../invalid/InvalidPatternMatching1.scala | 60 ++++++++++ .../invalid/InvalidPatternMatching2.scala | 60 ++++++++++ .../NonIntegralMain.scala | 0 .../genc/invalid/RecursiveADT1.scala | 26 ++++ .../genc/invalid/RecursiveADT2.scala | 17 +++ .../genc/unverified/Assignments.scala | 43 +++++++ .../regression/genc/valid/Aliasing1.scala | 49 ++++++++ .../regression/genc/valid/Aliasing2.scala | 29 +++++ .../regression/genc/valid/Aliasing3.scala | 52 ++++++++ .../genc/valid/DefaultParameter.scala | 17 +++ .../regression/genc/valid/EmptyClasses.scala | 61 ++++++++++ .../regression/genc/valid/Generics1.scala | 20 ++++ .../regression/genc/valid/Generics2.scala | 20 ++++ .../regression/genc/valid/Generics3.scala | 34 ++++++ .../regression/genc/valid/Generics4.scala | 20 ++++ .../regression/genc/valid/Generics5.scala | 20 ++++ .../regression/genc/valid/Inlining.scala | 46 ++++++++ .../regression/genc/valid/Option.scala | 73 ++++++++++++ .../regression/genc/valid/OptionFromLib.scala | 111 ++++++++++++++++++ .../genc/valid/PatternMatching1.scala | 58 +++++++++ .../genc/valid/PatternMatching2.scala | 61 ++++++++++ .../genc/valid/PatternMatching3.scala | 49 ++++++++ .../genc/valid/PatternMatching4.scala | 49 ++++++++ 24 files changed, 999 insertions(+) create mode 100644 src/test/resources/regression/genc/invalid/AsInstanceOf.scala create mode 100644 src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala create mode 100644 src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala rename src/test/resources/regression/genc/{unverified => invalid}/NonIntegralMain.scala (100%) create mode 100644 src/test/resources/regression/genc/invalid/RecursiveADT1.scala create mode 100644 src/test/resources/regression/genc/invalid/RecursiveADT2.scala create mode 100644 src/test/resources/regression/genc/unverified/Assignments.scala create mode 100644 src/test/resources/regression/genc/valid/Aliasing1.scala create mode 100644 src/test/resources/regression/genc/valid/Aliasing2.scala create mode 100644 src/test/resources/regression/genc/valid/Aliasing3.scala create mode 100644 src/test/resources/regression/genc/valid/DefaultParameter.scala create mode 100644 src/test/resources/regression/genc/valid/EmptyClasses.scala create mode 100644 src/test/resources/regression/genc/valid/Generics1.scala create mode 100644 src/test/resources/regression/genc/valid/Generics2.scala create mode 100644 src/test/resources/regression/genc/valid/Generics3.scala create mode 100644 src/test/resources/regression/genc/valid/Generics4.scala create mode 100644 src/test/resources/regression/genc/valid/Generics5.scala create mode 100644 src/test/resources/regression/genc/valid/Inlining.scala create mode 100644 src/test/resources/regression/genc/valid/Option.scala create mode 100644 src/test/resources/regression/genc/valid/OptionFromLib.scala create mode 100644 src/test/resources/regression/genc/valid/PatternMatching1.scala create mode 100644 src/test/resources/regression/genc/valid/PatternMatching2.scala create mode 100644 src/test/resources/regression/genc/valid/PatternMatching3.scala create mode 100644 src/test/resources/regression/genc/valid/PatternMatching4.scala diff --git a/src/test/resources/regression/genc/invalid/AsInstanceOf.scala b/src/test/resources/regression/genc/invalid/AsInstanceOf.scala new file mode 100644 index 000000000..c8eb4bbda --- /dev/null +++ b/src/test/resources/regression/genc/invalid/AsInstanceOf.scala @@ -0,0 +1,24 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object AsInstanceOf { + + abstract class Top + abstract class Middle extends Top + case class Bottom(x: Int) extends Middle + + // Middle is abstract and has a parent, and therefore shouldn't be used for casting + // because we don't support it yet. + def bad(x: Bottom) = x.asInstanceOf[Middle] + + def _main() = { + bad(Bottom(42)) + 0 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala b/src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala new file mode 100644 index 000000000..e584fcbbc --- /dev/null +++ b/src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala @@ -0,0 +1,60 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Odd { + def unapply(p: (Int, Int)): Option[Int] = { + val x = p._1 + if (x % 2 == 1) Some(x) else None[Int] + } +} + +object InvalidPatternMatching1 { + + def test(x: Option[(Int, Int)]) = x match { + case None() => 0 + case Some(Odd(y)) if y > 0 => y + case Some(Odd(y)) if y <= 0 => -y + case Some((a, b)) if a > b => a + case Some((a, b)) => b + } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) { + res = res * 2 + } + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(42, test(Some((42, 0)))) + + expect(42, test(Some((0, 42)))) + + expect(1, test(Some((-1, 99)))) + + expect(1, test(Some((1, 99)))) + + expect(0, test(None[(Int, Int)])) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + diff --git a/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala b/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala new file mode 100644 index 000000000..4f8897ed6 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala @@ -0,0 +1,60 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +case class Pair2(var x: Int, var y: Int) + +object Odd2 { + def unapply(p: Pair2): Option[Int] = { + p.y = p.y + 1 // WITHOUT THIS IT DOESN'T CRASH + val x = p.x + if (x == 1) Some(x) else None[Int] + } +} + +object InvalidPatternMatching2 { + + def test2(p: Pair2) = { + require(p.y == 0) + p match { + case Odd2(x) => p.y + case _ => 0 + } + } ensuring { res => res == p.y && p.y == 1 } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) { + res = res * 2 + } + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(1, test2(Pair2(1, 0))) + + expect(0, test2(Pair2(0, 0))) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + diff --git a/src/test/resources/regression/genc/unverified/NonIntegralMain.scala b/src/test/resources/regression/genc/invalid/NonIntegralMain.scala similarity index 100% rename from src/test/resources/regression/genc/unverified/NonIntegralMain.scala rename to src/test/resources/regression/genc/invalid/NonIntegralMain.scala diff --git a/src/test/resources/regression/genc/invalid/RecursiveADT1.scala b/src/test/resources/regression/genc/invalid/RecursiveADT1.scala new file mode 100644 index 000000000..06161c38b --- /dev/null +++ b/src/test/resources/regression/genc/invalid/RecursiveADT1.scala @@ -0,0 +1,26 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object RecursiveADT1 { + sealed abstract class Expr + + case class Add(e1: Expr, e2: Expr) extends Expr + case class Val(x: Int) extends Expr + + def eval(e: Expr): Int = e match { + case Val(x) => x + case Add(e1, e2) => eval(e1) + eval(e2) + } + + def example = eval(Add(Val(42), Val(58))) + + def _main() = { + example + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/invalid/RecursiveADT2.scala b/src/test/resources/regression/genc/invalid/RecursiveADT2.scala new file mode 100644 index 000000000..b3f6f72b6 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/RecursiveADT2.scala @@ -0,0 +1,17 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object RecursiveADT2 { + case class T(t: Option[T]) + + def _main() = { + val t = T(None[T]) + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/unverified/Assignments.scala b/src/test/resources/regression/genc/unverified/Assignments.scala new file mode 100644 index 000000000..19173e205 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/Assignments.scala @@ -0,0 +1,43 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +import leon.io.{ FileOutputStream => FOS } + +object Assignments { + + def output(x: Int, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fos.isOpen) + fos.write(x) && fos.write(" ") + } + + case class Wrapper(var b: Boolean) + + def compile() = { + implicit val state = leon.io.newState + + val fos = FOS.open("test.txt") + + val x = 42 + + val abort0 = false + val abort1 = abort0 || output(x, fos) + + var abort = false + abort = abort || output(x, fos) + + val w = Wrapper(false) + w.b = w.b || abort || output(x, fos) + + fos.close + } + + def _main() = { + compile() + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Aliasing1.scala b/src/test/resources/regression/genc/valid/Aliasing1.scala new file mode 100644 index 000000000..3ef6b813a --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing1.scala @@ -0,0 +1,49 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing1 { + + // Global variable are not allowed + // var g = 0 + + // This is illegal: + // def id(m: MutableInteger) = m + + case class MutableInteger(var x: Int) + + def editor(v: MutableInteger): Unit = { + v.x = 58 + } + + def foo(): Int = { + val mi = MutableInteger(42) + editor(mi) + mi.x + } ensuring { _ == 58 } + + def producer(x: Int) = MutableInteger(x + 10) + + def validator(v: MutableInteger) = { + if (v.x == 42) MutableInteger(58) + else { + v.x = 0 + MutableInteger(0) + } + } + + def bar(): Int = { + var mi1 = producer(32) + var mi2 = validator(mi1) + mi1.x + mi2.x + } ensuring { _ == 100 } + + def _main(): Int = { + if (foo() == 58 && bar() == 100) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Aliasing2.scala b/src/test/resources/regression/genc/valid/Aliasing2.scala new file mode 100644 index 000000000..81761a553 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing2.scala @@ -0,0 +1,29 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing2 { + + case class MutableInteger(var x: Int) + + def validator(v: MutableInteger) = { + if (v.x == 42) MutableInteger(58) + else { + v.x = 0 + MutableInteger(0) + } + } + + def testTemporary(): Int = { + validator(MutableInteger(42)).x + } ensuring { _ == 58 } + + def _main(): Int = { + if (testTemporary() == 58) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Aliasing3.scala b/src/test/resources/regression/genc/valid/Aliasing3.scala new file mode 100644 index 000000000..7b6506d33 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing3.scala @@ -0,0 +1,52 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing3 { + + case class MutableInteger(var x: Int) + + def scope = { + var x = 0 + + def hoo(m: MutableInteger) { + toto() + foo(42, m) + } + + def foo(y: Int, m: MutableInteger) { + toto() + x = 1 + bar(m) + m.x = y + } + + foo(42, MutableInteger(58)) + + val m = MutableInteger(58) + + hoo(m) + + def goo(y: Int) { + foo(y, m) + } + goo(42) + + m.x + } ensuring { _ == 42 } + + def bar(m: MutableInteger) { + m.x = 0 + } + + def toto() = { } + + def _main(): Int = { + if (scope == 42) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/DefaultParameter.scala b/src/test/resources/regression/genc/valid/DefaultParameter.scala new file mode 100644 index 000000000..0d5e4e500 --- /dev/null +++ b/src/test/resources/regression/genc/valid/DefaultParameter.scala @@ -0,0 +1,17 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object DefaultParameter { + + def foo(x: Int = 1) = x + + def bar = foo() + foo(2) == 3 + + def _main() = if (bar) 0 else 1 + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/valid/EmptyClasses.scala b/src/test/resources/regression/genc/valid/EmptyClasses.scala new file mode 100644 index 000000000..5a865a2bd --- /dev/null +++ b/src/test/resources/regression/genc/valid/EmptyClasses.scala @@ -0,0 +1,61 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object EmptyClasses { + // For most of the following classes, GenC will emit warnings and a byte field will be added + + case class Empty() + + case class Full(e: Empty) + + sealed abstract class Units() + case class Kg() extends Units + case class Lb() extends Units + + sealed abstract class Option + case class None() extends Option + case class Some(x: Int) extends Option + + def bar() = Empty() + + def testIf(b: Boolean) = if (b) Empty() else bar() + + def testOption(b: Boolean) = if (b) None() else Some(42) + + def testUnits(b: Boolean) = if (b) Kg() else Lb() + + // Compilation only + def _main() = { + val empty1 = Empty() + val empty2 = Empty() + + val empty3 = bar() + + val empty4 = testIf(true) + val empty5 = testIf(false) + + val full1 = Full(Empty()) + val full2 = Full(bar()) + val full3 = Full(empty1) + + val none = None() + val some = Some(58) + val option1 = testOption(true) + val option2 = testOption(false) + + val kg = Kg() + val lb = Lb() + + val kg2 = testUnits(true) + val lb2 = testUnits(false) + + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/valid/Generics1.scala b/src/test/resources/regression/genc/valid/Generics1.scala new file mode 100644 index 000000000..05dec39f3 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Generics1.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Generics1 { + case class Dummy[T](x: Int) + + def _main() = { + val d1 = Dummy[Int](42) + val d2 = Dummy[Char](58) + + if (d1.x + d2.x == 100) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Generics2.scala b/src/test/resources/regression/genc/valid/Generics2.scala new file mode 100644 index 000000000..46eb10d8d --- /dev/null +++ b/src/test/resources/regression/genc/valid/Generics2.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Generics2 { + case class Dummy[T](val t: T) + + def _main() = { + val d1 = Dummy(true) + val d2 = Dummy[Int](42) + + if (d1.t && d2.t == 42) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Generics3.scala b/src/test/resources/regression/genc/valid/Generics3.scala new file mode 100644 index 000000000..06c545bf7 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Generics3.scala @@ -0,0 +1,34 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Generics3 { + case class Dummy[T](val t: T) + + def fun[T](t: Option[T]): Option[Dummy[T]] = { + if (t.isDefined) Some(Dummy(t.get)) + else None[Dummy[T]] + } + + def test1(): Int = { + val none = None[Int] + if (fun(none).isDefined) 1 + else 0 + } ensuring { _ == 0 } + + def test2(): Int = { + val some = Some[Int](42) + val res = fun(some) + if (res.isDefined && res.get.t == 42) 0 + else 1 + } ensuring { _ == 0 } + + def _main() = { + test1() + test2() + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Generics4.scala b/src/test/resources/regression/genc/valid/Generics4.scala new file mode 100644 index 000000000..91c9bfb19 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Generics4.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Generics4 { + case class Dummy[T](val t: T) + + def fun[T](x: Int) = x + + def gun[T](x: T) = x + + def _main() = { + fun(58) - 58 /* == 0 */ + gun(42) - 42 /* == 0 */ + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Generics5.scala b/src/test/resources/regression/genc/valid/Generics5.scala new file mode 100644 index 000000000..fd4a6ae9f --- /dev/null +++ b/src/test/resources/regression/genc/valid/Generics5.scala @@ -0,0 +1,20 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object Generics5 { + case class Dummy[T](val t: T) + + def fun[T](x: Int) = x + + def gun[T](x: T) = x + + def _main() = { + fun[Int](58) - 58 /* == 0 */ + gun(42) - 42 /* == 0 */ + fun[Char](0) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Inlining.scala b/src/test/resources/regression/genc/valid/Inlining.scala new file mode 100644 index 000000000..5fda647ec --- /dev/null +++ b/src/test/resources/regression/genc/valid/Inlining.scala @@ -0,0 +1,46 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.{ extern, inline } + +object Inlining { + sealed abstract class Option[T] { + @inline + def map[U](f: T => U): Option[U] = this match { + case Some(x) => Some(f(x)) + case None() => None() + } + } + + case class Some[T](x: T) extends Option[T] + + case class None[T]() extends Option[T] + + def test1(): Int = { + def twice(opt: Option[Int]): Option[Int] = opt map { x => x * 2 } + + twice(Some(4)) match { + case Some(8) => 0 + case _ => -2 + } + } ensuring { res => res == 0 } + + def test2(): Int = { + var v = -1 + + def twice(opt: Option[Int]): Option[Int] = opt map { x => v = v + 1; x * 2 } + + twice(Some(4)) match { + case Some(8) => v + case _ => -2 + } + } ensuring { res => res == 0 } + + def _main() = { + test1() + test2() + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/valid/Option.scala b/src/test/resources/regression/genc/valid/Option.scala new file mode 100644 index 000000000..0d8c6d215 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Option.scala @@ -0,0 +1,73 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Option { + sealed abstract class Option[T] { + def get: T = { + require(isDefined) + //this match { case Some(x) => x } + this.asInstanceOf[Some[T]].x // slightly more efficient than pattern matching: no type checking + } + + def isEmpty = this match { + case Some(_) => false + case None() => true + } + + def nonEmpty = !isEmpty + def isDefined = nonEmpty + } + + case class Some[T](x: T) extends Option[T] + + case class None[T]() extends Option[T] + + case class Dummy1(x: Int) + case class Dummy2(opt: Option[Int]) + + def foo(x: Int): Option[Int] = { + if (x % 2 == 1) Some(x) + else None[Int] + } + + def bar(x: Int): Option[Dummy1] = { + if (x % 2 != 0) Some(Dummy1(x)) + else None[Dummy1] + } + + def baz(opt: Option[Int]): Dummy2 = { + Dummy2(opt) + } + + def test1(): Int = { + val o1 = foo(1) + val o2 = foo(2) + + if (o1.nonEmpty && o2.isEmpty && o1.get == 1) 0 + else 1 + } ensuring { _ == 0 } + + def test2(): Int = { + val o1 = bar(1) + val o2 = bar(2) + + if (o1.nonEmpty && o2.isEmpty && o1.get.x == 1) 0 + else 1 + } ensuring { _ == 0 } + + def test3(): Int = { + val o = baz(Some(42)) + + if (o.opt.isDefined && o.opt.get == 42) 0 + else 1 + } ensuring { _ == 0 } + + def _main() = { + test1() + test2() + test3() + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/OptionFromLib.scala b/src/test/resources/regression/genc/valid/OptionFromLib.scala new file mode 100644 index 000000000..272059ad9 --- /dev/null +++ b/src/test/resources/regression/genc/valid/OptionFromLib.scala @@ -0,0 +1,111 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object OptionFromLib { + case class Dummy1(val x: Int) + case class Dummy2(val opt: Option[Int]) + + def foo(x: Int): Option[Int] = { + require(x >= 0) + if (x % 2 == 1) Some(x) + else None[Int] + } + + def bar(x: Int): Option[Dummy1] = { + require(x >= 0) + if (x % 2 != 0) Some(Dummy1(x)) + else None[Dummy1] + } + + def baz(opt: Option[Int]): Dummy2 = { + Dummy2(opt) + } + + def funnyTwice(x: Int) = Some(x + x) + + def test1(): Int = { + val o1 = foo(1) + val o2 = foo(2) + + if (o1.nonEmpty && o2.isEmpty && o1.get == 1) 0 + else 1 + } ensuring { _ == 0 } + + def test2(): Int = { + val o1 = bar(1) + val o2 = bar(2) + + if (o1.nonEmpty && o2.isEmpty && o1.get.x == 1) 0 + else 1 + } ensuring { _ == 0 } + + def test3(): Int = { + val o = baz(Some(42)) + + if (o.opt.isDefined && o.opt.get == 42) 0 + else 1 + } ensuring { _ == 0 } + + // Below we test the inlining of several methods + + def testGetOrElse(): Int = { + Some(5) getOrElse { 6 } + } ensuring { _ == 5 } + + def testOrElse(x: Int): Int = { + require(x >= 0 && x < 2147483646) + foo(x) orElse { foo(x + 1) } get + } ensuring { res => + ((x % 2 == 1) ==> (res == x)) && + ((x % 2 == 0) ==> (res == x + 1)) + } + + def testMap(x: Int): Int = { + funnyTwice(x) map { y: Int => y + x } get + } ensuring { _ == x * 3 } + + def testFlatMap(x: Int): Int = { + funnyTwice(x) flatMap { y => funnyTwice(y) } get + } ensuring { _ == x * 4 } + + def testFilter(x: Int): Int = { + funnyTwice(x) filter { _ % 2 == 0 } get + } ensuring { _ == 2 * x } + + def testWithFilter(x: Int): Int = { + funnyTwice(x) withFilter { _ % 2 == 0 } get + } ensuring { _ == 2 * x } + + def testForall(x: Int): Int = { + if (funnyTwice(x) forall { _ % 2 == 0 }) 0 + else 1 + } ensuring { _ == 0 } + + def testExists(x: Int): Int = { + if (funnyTwice(x) exists { _ % 2 == 0 }) 0 + else 1 + } ensuring { _ == 0 } + + def _main() = { + test1() + + test2() + + test3() + + (testGetOrElse() - 5) + + (testOrElse(0) - 1) + + (testOrElse(1) - 1) + + (testMap(0)) + + (testMap(2) - 6) + + (testFlatMap(3) - 12) + + (testFilter(0)) + + (testFilter(-1) + 2) + + (testWithFilter(-50) + 100) + + (testForall(42)) + + (testExists(58)) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/PatternMatching1.scala b/src/test/resources/regression/genc/valid/PatternMatching1.scala new file mode 100644 index 000000000..60dcfac30 --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching1.scala @@ -0,0 +1,58 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object PatternMatching1 { + + def testLiteralSimple(x: Int) = x match { + case 42 => 0 + case 58 => 1 + case _ => 2 + } + + def testLiteralConditional(x: Int) = x match { + case y if y < 0 => -1 + case z if z == 0 => 0 + case a if a % 2 == 0 => 1 + case _ => 2 + } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) + res = res * 2 + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(0, testLiteralSimple(42)) + + expect(1, testLiteralSimple(58)) + + expect(2, testLiteralSimple(100)) + + expect(-1, testLiteralConditional(-10)) + + expect(0, testLiteralConditional(0)) + + expect(1, testLiteralConditional(16)) + + expect(2, testLiteralConditional(3)) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + diff --git a/src/test/resources/regression/genc/valid/PatternMatching2.scala b/src/test/resources/regression/genc/valid/PatternMatching2.scala new file mode 100644 index 000000000..9b7b97094 --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching2.scala @@ -0,0 +1,61 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object PatternMatching2 { + + def testScrutineeSideEffect(x: Int) = { + var local = 0 + + def inc() { local = local + 1 } + + // NOTE: using inc() or modifying local in foo or bar results in + // java.util.NoSuchElementException: key not found: def foo$0(a$4 : Int): Boolean = inc$0 + // or similar when verifying this file (due to the call being in the pattern matching guard statements) + // In any case, we DON'T want that! + def foo(a: Int): Boolean = { 42 == a } + def bar(a: Int): Boolean = { local == a } + + { inc(); x } match { + case y if foo(y) => local + case y if bar(1) => 0 + case _ => -1 + } + } ensuring { res => res != -1 } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) { + res = res * 2 + } + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(0, testScrutineeSideEffect(999)) + + expect(1, testScrutineeSideEffect(42)) + + expect(0, testScrutineeSideEffect(0)) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + diff --git a/src/test/resources/regression/genc/valid/PatternMatching3.scala b/src/test/resources/regression/genc/valid/PatternMatching3.scala new file mode 100644 index 000000000..0068c79ed --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching3.scala @@ -0,0 +1,49 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object PatternMatching3 { + + def testCaseClass(x: Option[Int]) = x match { + case y @ Some(z) if z > 0 => z + case zero: Some[Int] => 0 + case b @ _ => 999 + } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) { + res = res * 2 + } + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(42, testCaseClass(Some(42))) + + expect(0, testCaseClass(Some(0))) + + expect(999, testCaseClass(None[Int])) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + diff --git a/src/test/resources/regression/genc/valid/PatternMatching4.scala b/src/test/resources/regression/genc/valid/PatternMatching4.scala new file mode 100644 index 000000000..e10232abb --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching4.scala @@ -0,0 +1,49 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object PatternMatching4 { + + def testTuple(p: (Int, Int)) = p match { + case (0, 0) => 0 + case (x, y) if x > 0 => y + case (x, y) => -y + } + + def power2(pow: Int): Int = { + require(pow < 31 && pow >= 0) + + var res = 1 + var n = pow + + while (n > 0) { + res = res * 2 + } + + res + } + + def _main() = { + var testCount = 0 + + def expect(value: Int, actual: Int): Int = { + require(testCount >= 0 && testCount < 30) + + testCount = testCount + 1 + + if (value == actual) 0 + else power2(testCount) + } + + expect(0, testTuple((0, 0))) + + expect(5, testTuple((0, -5))) + + expect(-5, testTuple((1, -5))) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + From 86a0538e57bfdf6c7744ab8944598c827fc25a97 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 8 Nov 2016 17:26:36 +0100 Subject: [PATCH 03/77] Clean slate for GenC Removed GenC in view of GenC v2 --- src/main/scala/leon/genc/Builder.scala | 213 --------- src/main/scala/leon/genc/CAST.scala | 438 ------------------ src/main/scala/leon/genc/CConverter.scala | 26 -- .../scala/leon/genc/CFileOutputPhase.scala | 54 --- src/main/scala/leon/genc/CPrinter.scala | 265 ----------- src/main/scala/leon/genc/CPrinterHelper.scala | 86 ---- src/main/scala/leon/genc/ExtraOps.scala | 68 --- src/main/scala/leon/genc/GenerateCPhase.scala | 20 - src/main/scala/leon/genc/MiniReporter.scala | 45 -- src/main/scala/leon/genc/Normaliser.scala | 76 --- src/main/scala/leon/genc/TypeAnalyser.scala | 48 -- .../leon/genc/converters/ClassConverter.scala | 209 --------- .../leon/genc/converters/Converters.scala | 306 ------------ .../leon/genc/converters/FunConverter.scala | 183 -------- .../genc/converters/GenericConverter.scala | 38 -- .../leon/genc/converters/ProgConverter.scala | 177 ------- 16 files changed, 2252 deletions(-) delete mode 100644 src/main/scala/leon/genc/Builder.scala delete mode 100644 src/main/scala/leon/genc/CAST.scala delete mode 100644 src/main/scala/leon/genc/CConverter.scala delete mode 100644 src/main/scala/leon/genc/CFileOutputPhase.scala delete mode 100644 src/main/scala/leon/genc/CPrinter.scala delete mode 100644 src/main/scala/leon/genc/CPrinterHelper.scala delete mode 100644 src/main/scala/leon/genc/ExtraOps.scala delete mode 100644 src/main/scala/leon/genc/GenerateCPhase.scala delete mode 100644 src/main/scala/leon/genc/MiniReporter.scala delete mode 100644 src/main/scala/leon/genc/Normaliser.scala delete mode 100644 src/main/scala/leon/genc/TypeAnalyser.scala delete mode 100644 src/main/scala/leon/genc/converters/ClassConverter.scala delete mode 100644 src/main/scala/leon/genc/converters/Converters.scala delete mode 100644 src/main/scala/leon/genc/converters/FunConverter.scala delete mode 100644 src/main/scala/leon/genc/converters/GenericConverter.scala delete mode 100644 src/main/scala/leon/genc/converters/ProgConverter.scala diff --git a/src/main/scala/leon/genc/Builder.scala b/src/main/scala/leon/genc/Builder.scala deleted file mode 100644 index 2e10d9f1c..000000000 --- a/src/main/scala/leon/genc/Builder.scala +++ /dev/null @@ -1,213 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import purescala.Common._ -import purescala.Definitions._ -import purescala.Expressions._ -import purescala.Types._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -private[genc] trait Builder { - this: CConverter => - - def buildVar(id: Identifier, typ: TypeTree)(implicit funCtx: FunCtx) = - CAST.Var(convertToId(id), convertToType(typ)) - - def buildVal(id: Identifier, typ: TypeTree)(implicit funCtx: FunCtx) = - CAST.Val(convertToId(id), convertToType(typ)) - - def buildAccessVar(id1: Identifier)(implicit funCtx: FunCtx) = { - // Depending on the context, we have to deference the variable - val id = convertToId(id1) - if (funCtx.hasOuterVar(id)) CAST.AccessRef(id) - else CAST.AccessVar(id) - } - - def buildLet(id: Identifier, value: Expr, rest1: Expr, forceVar: Boolean) - (implicit funCtx: FunCtx): CAST.Stmt = { - // Handle special case with Unit: don't create a var - if (value.getType == UnitType) convertToStmt(value) ~ convertToStmt(rest1) - else { - val (x, stmt) = buildDeclInitVar(id, value, forceVar) - - // Augment ctx for the following instructions - val funCtx2 = funCtx.extend(x) - val rest = convertToStmt(rest1)(funCtx2) - - stmt ~ rest - } - } - - // Create a new variable for the given value, potentially immutable, and initialize it - def buildDeclInitVar(id: Identifier, v: Expr, forceVar: Boolean) - (implicit funCtx: FunCtx): (CAST.Var, CAST.Stmt) = { - val valueF = convertAndFlatten(v) - val typ = v.getType - - valueF.value match { - case CAST.IfElse(cond, thn, elze) => - val x = buildVar(id, typ) - val decl = CAST.DeclVar(x) - val ifelse = buildIfElse(cond, injectAssign(x, thn), injectAssign(x, elze)) - val init = decl ~ ifelse - - (x, valueF.body ~~ init) - - case value => - val x = if (forceVar) buildVar(id, typ) else buildVal(id, typ) - val init = CAST.DeclInitVar(x, value) - - (x, valueF.body ~~ init) - } - } - - def buildBinOp(lhs: Expr, op: String, rhs: Expr)(implicit funCtx: FunCtx) = { - buildMultiOp(op, lhs :: rhs :: Nil) - } - - def buildBinOp(lhs: CAST.Stmt, op: String, rhs: CAST.Stmt) = { - CAST.Op(op, lhs, rhs) - } - - def buildUnOp(op: String, rhs1: Expr)(implicit funCtx: FunCtx) = { - val rhsF = convertAndFlatten(rhs1) - rhsF.body ~~ CAST.Op(op, rhsF.value) - } - - def buildMultiOp(op: String, exprs: Seq[Expr])(implicit funCtx: FunCtx): CAST.Stmt = { - require(exprs.length >= 2) - - val stmts = exprs map convertToStmt - val types = exprs map { e => convertToType(e.getType) } - - buildMultiOp(op, stmts, types) - } - - def buildMultiOp(op: String, stmts: Seq[CAST.Stmt], types: Seq[CAST.Type]): CAST.Stmt = { - // Default operator constuction when either pure statements are involved - // or no shortcut can happen - def defaultBuild = { - val fs = normaliseExecution(stmts, types) - fs.bodies ~~ CAST.Op(op, fs.values) - } - - if (stmts forall { _.isPureValue }) defaultBuild - else op match { - case "&&" => - // Apply short-circuit if needed - if (stmts.length == 2) { - // Base case: - // { { a; v } && { b; w } } - // is mapped onto - // { a; if (v) { b; w } else { false } } - val av = flatten(stmts(0)) - val bw = stmts(1) - - if (bw.isPureValue) defaultBuild - else av.body ~~ buildIfElse(av.value, bw, CAST.False) - } else { - // Recursive case: - // { { a; v } && ... } - // is mapped onto - // { a; if (v) { ... } else { false } } - val av = flatten(stmts(0)) - val rest = buildMultiOp(op, stmts.tail, types.tail) - - if (rest.isPureValue) defaultBuild - else av.body ~~ buildIfElse(av.value, rest, CAST.False) - } - - case "||" => - // Apply short-circuit if needed - if (stmts.length == 2) { - // Base case: - // { { a; v } || { b; w } } - // is mapped onto - // { a; if (v) { true } else { b; w } } - val av = flatten(stmts(0)) - val bw = stmts(1) - - if (bw.isPureValue) defaultBuild - else av.body ~~ buildIfElse(av.value, CAST.True, bw) - } else { - // Recusrive case: - // { { a; v } || ... } - // is mapped onto - // { a; if (v) { true } else { ... } } - val av = flatten(stmts(0)) - val rest = buildMultiOp(op, stmts.tail, types.tail) - - if (rest.isPureValue) defaultBuild - else av.body ~~ buildIfElse(av.value, CAST.True, rest) - } - - case _ => - defaultBuild - } - } - - // Flatten `if (if (cond1) thn1 else elze1) thn2 else elze2` - // into `if (cond1) { if (thn1) thn2 else elz2 } else { if (elz1) thn2 else elze2 }` - // or, if possible, into `if ((cond1 && thn1) || elz1) thn2 else elz2` - // - // Flatten `if (true) thn else elze` into `thn` - // Flatten `if (false) thn else elze` into `elze` - def buildIfElse(cond: CAST.Stmt, thn2: CAST.Stmt, elze2: CAST.Stmt): CAST.Stmt = { - val condF = flatten(cond) - - val ifelse = condF.value match { - case CAST.IfElse(cond1, thn1, elze1) => - if (cond1.isPure && thn1.isPure && elze1.isPure) { - val bools = CAST.Bool :: CAST.Bool :: Nil - val ands = cond1 :: thn1 :: Nil - val ors = buildMultiOp("&&", ands, bools) :: elze1 :: Nil - val condX = buildMultiOp("||", ors, bools) - CAST.IfElse(condX, thn2, elze2) - } else { - buildIfElse(cond1, buildIfElse(thn1, thn2, elze2), buildIfElse(elze1, thn2, elze2)) - } - - case CAST.True => thn2 - case CAST.False => elze2 - case cond2 => CAST.IfElse(cond2, thn2, elze2) - } - - condF.body ~~ ifelse - } - - def injectReturn(stmt: CAST.Stmt): CAST.Stmt = { - val f = flatten(stmt) - - f.value match { - case CAST.IfElse(cond, thn, elze) => - f.body ~~ CAST.IfElse(cond, injectReturn(thn), injectReturn(elze)) - - // FIXME this is an hugly hack. Fix it when Error are supported in `convert` - case CAST.NoStmt => - f.body ~~ CAST.NoStmt - - case _ => - f.body ~~ CAST.Return(f.value) - } - } - - def injectAssign(x: CAST.Var, stmt: CAST.Stmt): CAST.Stmt = { - injectAssign(CAST.AccessVar(x.id), stmt) - } - - def injectAssign(x: CAST.Stmt, stmt: CAST.Stmt): CAST.Stmt = { - val f = flatten(stmt) - - f.value match { - case CAST.IfElse(cond, thn, elze) => - f.body ~~ CAST.IfElse(cond, injectAssign(x, thn), injectAssign(x, elze)) - - case _ => - f.body ~~ CAST.Assign(x, f.value) - } - } - -} - diff --git a/src/main/scala/leon/genc/CAST.scala b/src/main/scala/leon/genc/CAST.scala deleted file mode 100644 index b03019c0a..000000000 --- a/src/main/scala/leon/genc/CAST.scala +++ /dev/null @@ -1,438 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import utils.{ Position, UniqueCounter } - -/* - * Here are defined classes used to represent AST of C programs. - * - * NOTE on char and string: because the C character and string literals - * encoding sets are highly dependent on platforms and compilers, only - * basic single-byte characters from the ASCII set are supported at the - * moment. - * - * Details on such literals can be found in the C99 standard in §3.7, - * §6.4.4.4 and §6.4.5, and more. - */ - -object CAST { // C Abstract Syntax Tree - - /* ------------------------------------------------------------- Tree ----- */ - sealed abstract class Tree - case object NoTree extends Tree - - - /* ------------------------------------------------------------ Types ----- */ - abstract class Type(val rep: String) extends Tree { - override def toString = rep - - def name: String = rep // usefull when the type name should be used as a variable identifier - } - case object NoType extends Type("???") // Used in place of a dropped type - - /* Type Modifiers */ - case class Const(typ: Type) extends Type(s"$typ const") { - override def name: String = s"${typ.name}_const" - } - case class Pointer(typ: Type) extends Type(s"$typ*") { - override def name: String = s"${typ.name}_ptr" - } - - /* Primitive Types */ - case object Char extends Type("char") // See NOTE on char & string - case object Int32 extends Type("int32_t") // Requires - case object Bool extends Type("bool") // Requires - case object Void extends Type("void") - - /* Compound Types */ - // In order to identify structs and unions, we need to make sure they have an Id (this is used by ProgConverter). - trait Identification { val id: Id } - type TypeWithId = Type with Identification - case class Struct(id: Id, fields: Seq[Var]) extends Type(id.name) with Identification - // For union, use the factory - class Union private (val id: Id, val fields: Seq[(Type, Id)]) extends Type(id.name) with Identification - - /* (Basic) String Type */ - // NOTE It might be better to have data+length structure - // NOTE Currently, only string literals are supported, hence they can legally - // be returned from functions. - case object String extends Type("char*") { - override def name: String = "string" - } - - /* Typedef */ - case class Typedef(orig: Id, alias: Id) extends Type(alias.name) - - /* Enum */ - case class Enum(id: Id, values: Seq[EnumLiteral]) extends Type(id.name) - - - /* --------------------------------------------------------- Literals ----- */ - case class CharLiteral(c: Char) extends Stmt { - require(isASCII(c)) // See NOTE on char & string - } - - case class IntLiteral(v: Int) extends Stmt - - case class BoolLiteral(b: Boolean) extends Stmt - - case class StringLiteral(s: String) extends Stmt { - require(isASCII(s)) // See NOTE on char & string - } - - case class EnumLiteral(id: Id) extends Stmt - - - /* ----------------------------------------------------- Definitions ----- */ - abstract class Def extends Tree - - case class Include(file: String) extends Def { - require(!file.isEmpty && isASCII(file)) - } - - case class Prog( - includes: Set[Include], - typedefs: Seq[Typedef], - enums: Seq[Enum], - types: Seq[Type], - functions: Seq[Fun] - ) extends Def - - // Manually defined function through the cCode.function annotation have a string - // for signature+body instead of the usual Stmt AST exclusively for the body - case class Fun(id: Id, retType: Type, params: Seq[Var], body: Either[Stmt, String]) extends Def - - case class Id(name: String) extends Def { - // TODO add check on name's domain for conformance - - // `|` is used as the margin delimiter and can cause trouble in some situations - def fixMargin = - if (name.size > 0 && name(0) == '|') "| " + name - else name - } - - case class Var(id: Id, typ: Type) extends Def { - require(!typ.isVoid) - } - - /* ----------------------------------------------------------- Stmts ----- */ - abstract class Stmt extends Tree - case object NoStmt extends Stmt - - case class Compound(stmts: Seq[Stmt]) extends Stmt - - case class Assert(pred: Stmt, error: Option[String]) extends Stmt { // Requires - require(pred.isValue) - } - - case class DeclVar(x: Var) extends Stmt - - case class DeclInitVar(x: Var, value: Stmt) extends Stmt { - require(value.isValue) - } - - case class Assign(lhs: Stmt, rhs: Stmt) extends Stmt { - require(lhs.isValue && rhs.isValue) - } - - // Note: we don't need to differentiate between specific - // operators so we only keep track of the "kind" of operator - // with an Id. - case class UnOp(op: Id, rhs: Stmt) extends Stmt { - require(rhs.isValue) - } - - case class MultiOp(op: Id, stmts: Seq[Stmt]) extends Stmt { - require(stmts.length > 1 && stmts.forall { _.isValue }) - } - - case class SubscriptOp(ptr: Stmt, idx: Stmt) extends Stmt { - require(ptr.isValue && idx.isValue) - } - - case object Break extends Stmt - - case class Return(stmt: Stmt) extends Stmt { - require(stmt.isValue) - } - - case class IfElse(cond: Stmt, thn: Stmt, elze: Stmt) extends Stmt { - require(cond.isValue) - } - - case class While(cond: Stmt, body: Stmt) extends Stmt { - require(cond.isValue) - } - - case class AccessVar(id: Id) extends Stmt - case class AccessRef(id: Id) extends Stmt - case class AccessAddr(id: Id) extends Stmt - case class AccessField(struct: Stmt, field: Id) extends Stmt { - require(struct.isValue) - } - - case class Call(id: Id, args: Seq[Stmt]) extends Stmt { - require(args forall { _.isValue }) - } - - case class StructInit(args: Seq[(Id, Stmt)], struct: Struct) extends Stmt { - require(args forall { _._2.isValue }) - } - - case class ArrayInit(length: Stmt, valueType: Type, defaultValue: Stmt) extends Stmt { - require(length.isValue && defaultValue.isValue) - } - - case class ArrayInitWithValues(valueType: Type, values: Seq[Stmt]) extends Stmt { - require(values forall { _.isValue }) - - lazy val length = values.length - } - - - /* -------------------------------------------------------- Factories ----- */ - object Literal { - def apply(c: Char)(implicit pos: Position): CharLiteral = { - if (isASCII(c)) CharLiteral(c) - else unsupported("Character literals are restricted to the ASCII set") - } - - def apply(s: String)(implicit pos: Position): StringLiteral = { - if (isASCII(s)) StringLiteral(s) - else unsupported("String literals are restricted to the ASCII set") - } - - def apply(i: Int) = IntLiteral(i) - def apply(b: Boolean) = BoolLiteral(b) - def apply(u: Unit) = NoStmt - } - - object Op { - def apply(op: String, rhs: Stmt) = UnOp(Id(op), rhs) - def apply(op: String, rhs: Stmt, lhs: Stmt) = MultiOp(Id(op), rhs :: lhs :: Nil) - def apply(op: String, stmts: Seq[Stmt]) = MultiOp(Id(op), stmts) - } - - object Val { - def apply(id: Id, typ: Type) = typ match { - case Const(_) => Var(id, typ) // avoid const of const - case _ => Var(id, Const(typ)) - } - } - - /* "Templatetized" Types */ - object Tuple { - def apply(bases: Seq[Type]) = { - val name = Id("__leon_tuple_" + bases.mkString("_") + "_t") - - val fields = bases.zipWithIndex map { - case (typ, idx) => Var(getNthId(idx + 1), typ) - } - - Struct(name, fields) - } - - // Indexes start from 1, not 0! - def getNthId(n: Int) = Id("_" + n) - } - - object Array { - def apply(base: Type) = { - val name = Id("__leon_array_" + base + "_t") - val data = Var(dataId, Pointer(base)) - val length = Var(lengthId, Int32) - val fields = data :: length :: Nil - - Struct(name, fields) - } - - def lengthId = Id("length") - def dataId = Id("data") - } - - object Union { - def apply(id: Id, types: Seq[Type]): Union = { - val unionMembers = types map { t => (t, valueForType(t)) } - new Union(id, unionMembers) - } - - def unapply(u: Union): Option[(Id, Seq[(Type, Id)])] = { - Some((u.id, u.fields)) - } - - // The "value" here refers to the identifier inside the union for inheritance. - def valueForType(t: Type) = Id(t.name + "_value") - def valuePathForType(t: Type) = Id("value." + t.name + "_value") - } - - object Enum { - // The "tag" here refers to the enumeration value used to identify a type for inheritance. - def tagForType(t: Type) = EnumLiteral(Id("tag_" + t.name)) - } - - - /* ---------------------------------------------------- Introspection ----- */ - implicit class IntrospectionOps(val stmt: Stmt) { - def isLiteral = stmt match { - case _: CharLiteral => true - case _: IntLiteral => true - case _: BoolLiteral => true - case _: StringLiteral => true - case _: EnumLiteral => true - case _ => false - } - - // True if statement can be used as a value - def isValue: Boolean = isLiteral || { - stmt match { - //case _: Assign => true it's probably the case but for now let's ignore it - case c: Compound => c.stmts.size == 1 && c.stmts.head.isValue - case _: UnOp => true - case _: MultiOp => true - case _: SubscriptOp => true - case _: AccessVar => true - case _: AccessRef => true - case _: AccessAddr => true - case _: AccessField => true - case _: Call => true - case _: StructInit => true - case _: ArrayInit => true - case _: ArrayInitWithValues => true - case _ => false - } - } - - def isPure: Boolean = isLiteral || { - stmt match { - case NoStmt => true - case Compound(stmts) => stmts forall { _.isPure } - case Assert(pred, _) => pred.isPure - case UnOp(_, rhs) => rhs.isPure - case MultiOp(_, stmts) => Compound(stmts).isPure - case SubscriptOp(ptr, idx) => (ptr ~ idx).isPure - case IfElse(c, t, e) => (c ~ t ~ e).isPure - case While(c, b) => (c ~ b).isPure - case AccessVar(_) => true - case AccessRef(_) => true - case AccessAddr(_) => true - case AccessField(s, _) => s.isPure - // case Call(id, args) => true if args are pure and function `id` is pure too - case _ => false - } - } - - def isPureValue = isValue && isPure - } - - - /* ------------------------------------------------------------- DSL ----- */ - // Operator ~~ appends and flattens nested compounds - implicit class StmtOps(val stmt: Stmt) { - // In addition to combining statements together in a compound - // we remove the empty ones and if the resulting compound - // has only one statement we return this one without being - // wrapped into a Compound - def ~(other: Stmt) = { - val stmts = (stmt, other) match { - case (Compound(stmts), Compound(others)) => stmts ++ others - case (stmt , Compound(others)) => stmt +: others - case (Compound(stmts), other ) => stmts :+ other - case (stmt , other ) => stmt :: other :: Nil - } - - def isNoStmt(s: Stmt) = s match { - case NoStmt => true - case _ => false - } - - val compound = Compound(stmts filterNot isNoStmt) - compound match { - case Compound(stmts) if stmts.length == 0 => NoStmt - case Compound(stmts) if stmts.length == 1 => stmts.head - case compound => compound - } - } - - def ~~(others: Seq[Stmt]) = stmt ~ Compound(others) - } - - implicit class StmtsOps(val stmts: Seq[Stmt]) { - def ~~(other: Stmt) = other match { - case Compound(others) => Compound(stmts) ~~ others - case other => Compound(stmts) ~ other - } - - def ~~~(others: Seq[Stmt]) = Compound(stmts) ~~ others - } - - val True = BoolLiteral(true) - val False = BoolLiteral(false) - - - implicit class TypeOps(val typ: Type) { - // Test whether a given type is made of void - def isVoid: Boolean = typ match { - case Void => true - case Const(t) => t.isVoid - case Pointer(t) => t.isVoid // TODO is this a good idea since it can represent anything? - case _ => false - } - - // Remove any const qualifier from the given type - def removeConst: Type = typ match { - case Const(t) => t.removeConst - case _ => typ - } - } - - - /* ------------------------------------------------ Fresh Generators ----- */ - object FreshId { - private var counter = -1 - private val leonPrefix = "__leon_" - - def apply(prefix: String = ""): Id = { - counter += 1 - Id(leonPrefix + prefix + counter) - } - } - - object FreshVar { - def apply(typ: Type, prefix: String = "") = Var(FreshId(prefix), typ) - } - - object FreshVal { - def apply(typ: Type, prefix: String = "") = Val(FreshId(prefix), typ) - } - - def generateMain(_mainId: Id, return_mainResult: Boolean): Fun = { - val id = Id("main") - val retType = Int32 - val argc = Var(Id("argc"), Int32) - val argv = Var(Id("argv"), Pointer(Pointer(Char))) - val params = argc :: argv :: Nil - - val body = - if (return_mainResult) Return(Call(_mainId, Nil)) - else Call(_mainId, Nil) ~ Return(IntLiteral(0)) - - val main = Fun(id, retType, params, Left(body)) - - main - } - - - /* ---------------------------------------------------------- Details ----- */ - // String & char limitations, see NOTE above - private def isASCII(c: Char): Boolean = { c >= 0 && c <= 127 } - private def isASCII(s: String): Boolean = s forall isASCII - - // Type of exception used to report unexpected or unsupported features - final case class ConversionError(error: String, pos: Position) extends Exception(error) - - private[genc] def unsupported(detail: String)(implicit pos: Position) = - throw ConversionError(s"Unsupported feature: $detail", pos) -} - diff --git a/src/main/scala/leon/genc/CConverter.scala b/src/main/scala/leon/genc/CConverter.scala deleted file mode 100644 index dda7095b5..000000000 --- a/src/main/scala/leon/genc/CConverter.scala +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import converters._ - -import purescala.Definitions._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -class CConverter(val ctx: LeonContext, val prog: Program) -extends Builder with Normaliser with TypeAnalyser with Converters with MiniReporter { - // Conversion entry point - def convert: CAST.Prog = try { - convertToProg - } catch { - case e @ CAST.ConversionError(error, pos) => - val msg = s"GenC repported the following error:\n$error" - - debug(e) - - ctx.reporter.fatalError(pos, msg) - } - -} - diff --git a/src/main/scala/leon/genc/CFileOutputPhase.scala b/src/main/scala/leon/genc/CFileOutputPhase.scala deleted file mode 100644 index 2e687e0b7..000000000 --- a/src/main/scala/leon/genc/CFileOutputPhase.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import java.io.File -import java.io.FileWriter -import java.io.BufferedWriter - -object CFileOutputPhase extends UnitPhase[CAST.Prog] { - - val name = "C File Output" - val description = "Output converted C program to the specified file (default leon.c)" - - val optOutputFile = new LeonOptionDef[String] { - val name = "o" - val description = "Output file" - val default = "leon.c" - val usageRhs = "file" - val parser = OptionParsers.stringParser - } - - override val definedOptions: Set[LeonOptionDef[Any]] = Set(optOutputFile) - - def apply(ctx: LeonContext, program: CAST.Prog) { - // Get the output file name from command line options, or use default - val outputFile = new File(ctx.findOptionOrDefault(optOutputFile)) - val parent = outputFile.getParentFile() - try { - if (parent != null) { - parent.mkdirs() - } - } catch { - case _ : java.io.IOException => ctx.reporter.fatalError("Could not create directory " + parent) - } - - // Output C code to the file - try { - val fstream = new FileWriter(outputFile) - val out = new BufferedWriter(fstream) - - val p = new CPrinter - p.print(program) - - out.write(p.toString) - out.close() - - ctx.reporter.info(s"Output written to $outputFile") - } catch { - case _ : java.io.IOException => ctx.reporter.fatalError("Could not write on " + outputFile) - } - } - -} diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala deleted file mode 100644 index 885e1c35c..000000000 --- a/src/main/scala/leon/genc/CPrinter.scala +++ /dev/null @@ -1,265 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import CAST._ -import CPrinterHelpers._ - -class CPrinter(val sb: StringBuffer = new StringBuffer) { - override def toString = sb.toString - - def print(tree: Tree) = pp(tree)(PrinterContext(0, this)) - - private def escape(c: Char): String = c match { - case '\b' => "\\b" - case '\t' => "\\t" - case '\n' => "\\n" - case '\f' => "\\f" - case '\r' => "\\r" - case '\\' => "\\\\" - case '\'' => "\\'" - case '\"' => "\\\"" - case c => c.toString - } - - private def escape(s: String): String = { - import org.apache.commons.lang3.StringEscapeUtils - StringEscapeUtils.escapeJava(s) - } - - private[genc] def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = tree match { - /* ---------------------------------------------------------- Types ----- */ - case typ: Type => c"${typ.rep}" - - - /* ------------------------------------------------------- Literals ----- */ - case CharLiteral(c) => c"'${escape(c)}'" - case IntLiteral(v) => c"$v" - case BoolLiteral(b) => c"$b" - - // Mind the fourth and eighth double quotes - case StringLiteral(s) => c""""${escape(s)}"""" - - case EnumLiteral(id) => c"$id" - - - /* --------------------------------------------------- Definitions ----- */ - case Prog(includes, typedefs, enums, types, functions) => - c"""|/* ------------------------------------ includes ----- */ - | - |${nary(buildIncludes(includes), sep = "\n")} - | - |/* -------------------------------- type aliases ----- */ - | - |${nary(typedefs map TypedefDecl, sep = "\n")} - | - |/* --------------------------------------- enums ----- */ - | - |${nary(enums map EnumDef, sep = "\n")} - | - |/* ----------------------- data type definitions ----- */ - | - |${nary(types map TypeDef, sep = "\n")} - | - |/* ----------------------- function declarations ----- */ - | - |${nary(functions map FunDecl, sep = "\n")} - | - |/* ------------------------ function definitions ----- */ - | - |${nary(functions, sep = "\n")} - |""" - - // Manually defined function - case Fun(_, _, _, Right(function)) => - c"$function" - - // Auto-generated function - case f @ Fun(_, _, _, Left(body: Compound)) => - c"""|${FunSign(f)} - |{ - | $body - |} - |""" - - // Quick'n'dirty hack to ensure one ';' close the body - case Fun(id, retType, params, Left(stmt)) => - c"${Fun(id, retType, params, Left(Compound(Seq(stmt))))}" - - case Id(name) => c"$name" - - - /* --------------------------------------------------------- Stmts ----- */ - case NoStmt => c"/* empty */" - - case Compound(stmts) => - val lastIdx = stmts.length - 1 - - for ((stmt, idx) <- stmts.zipWithIndex) { - if (stmt.isValue) c"$stmt;" - else c"$stmt" - - if (idx != lastIdx) - c"$NewLine" - } - - case Assert(pred, Some(error)) => c"assert($pred); /* $error */" - case Assert(pred, None) => c"assert($pred);" - - case Var(id, _) => c"$id" - case DeclVar(Var(id, typ)) => c"$typ $id;" - - // If the length is a literal we don't need VLA - case DeclInitVar(Var(id, typ), ai @ ArrayInit(IntLiteral(length), _, _)) => - val buffer = FreshId("buffer") - val values = for (i <- 0 until length) yield ai.defaultValue - c"""|${ai.valueType} $buffer[${ai.length}] = { $values }; - |$typ $id = { .length = ${ai.length}, .data = $buffer }; - |""" - - // TODO depending on the type of array (e.g. `char`) or the value (e.g. `0`), we could use `memset`. - case DeclInitVar(Var(id, typ), ai: ArrayInit) => // Note that `typ` is a struct here - val buffer = FreshId("vla_buffer") - val i = FreshId("i") - c"""|${ai.valueType} $buffer[${ai.length}]; - |for (${Int32} $i = 0; $i < ${ai.length}; ++$i) { - | $buffer[$i] = ${ai.defaultValue}; - |} - |$typ $id = { .length = ${ai.length}, .data = $buffer }; - |""" - - case DeclInitVar(Var(id, typ), ai: ArrayInitWithValues) => // Note that `typ` is a struct here - val buffer = FreshId("buffer") - c"""|${ai.valueType} $buffer[${ai.length}] = { ${ai.values} }; - |$typ $id = { .length = ${ai.length}, .data = $buffer }; - |""" - - case DeclInitVar(Var(id, typ), value) => - c"$typ $id = $value;" - - case Assign(lhs, rhs) => - c"$lhs = $rhs;" - - case UnOp(op, rhs) => c"($op$rhs)" - case MultiOp(op, stmts) => c"""${nary(stmts, sep = s" ${op.fixMargin} ", - opening = "(", closing = ")")}""" - case SubscriptOp(ptr, idx) => c"$ptr[$idx]" - - case Break => c"break;" - case Return(stmt) => c"return $stmt;" - - case IfElse(cond, thn: Compound, elze: Compound) => - c"""|if ($cond) - |{ - | $thn - |} - |else - |{ - | $elze - |} - |""" - - case IfElse(cond, thn: Compound, elze) => pp(IfElse(cond, thn, Compound(Seq(elze)))) - case IfElse(cond, thn, elze: Compound) => pp(IfElse(cond, Compound(Seq(thn)), elze)) - case IfElse(cond, thn, elze) => pp(IfElse(cond, Compound(Seq(thn)), Compound(Seq(elze)))) - - case While(cond, body) => - c"""|while ($cond) - |{ - | $body - |} - |""" - - case AccessVar(id) => c"$id" - case AccessRef(id) => c"(*$id)" - case AccessAddr(id) => c"(&$id)" - case AccessField(struct, field) => c"$struct.$field" - case Call(id, args) => c"$id($args)" - - case StructInit(args, struct) => - c"(${struct.id}) { " - for ((id, stmt) <- args.init) { - c".$id = $stmt, " - } - if (!args.isEmpty) { - val (id, stmt) = args.last - c".$id = $stmt " - } - c"}" - - /* --------------------------------------------------------- Error ----- */ - case tree => sys.error(s"CPrinter: <<$tree>> was not handled properly") - } - - - private[genc] def pp(wt: WrapperTree)(implicit ctx: PrinterContext): Unit = wt match { - case TypedefDecl(Typedef(Id(orig), Id(alias))) => - c"typedef $alias $orig;" - - case FunDecl(f) => - c"${FunSign(f)};$NewLine" - - case FunSign(Fun(id, retType, Nil, _)) => - c"""|$retType - |$id($Void)""" - - case FunSign(Fun(id, retType, params, _)) => - c"""|$retType - |$id(${nary(params map DeclParam)})""" - - case DeclParam(Var(id, typ)) => - c"$typ $id" - - case EnumDef(Enum(name, values)) => - c"""|typedef enum $name { - | ${nary(values, sep = ",\n")} - |} $name; - |""" - - case TypeDef(s: Struct) => - c"${StructDef(s)}" - - case TypeDef(u: Union) => - c"${UnionDef(u)}" - - case StructDef(Struct(name, fields)) => - c"""|typedef struct { - | ${nary(fields map DeclParam, sep = ";\n", closing = ";")} - |} $name; - |""" - - case UnionDef(Union(name, fields)) => - c"""|typedef union { - | ${nary(fields map { case (typ, id) => UnionValueDef(typ, id) }, sep = "\n")} - |} $name; - |""" - - case UnionValueDef(typ, id) => - c"$typ $id;" - - case NewLine => - c"""| - |""" - } - - /** Hardcoded list of required include files from C standard library **/ - private lazy val includes_ = Set("assert.h", "stdbool.h", "stdint.h") map Include - - private def buildIncludes(includes: Set[Include]): Seq[String] = - (includes_ ++ includes).toSeq sortBy { _.file } map { i => s"#include <${i.file}>" } - - /** Wrappers to distinguish how the data should be printed **/ - private[genc] sealed abstract class WrapperTree - private case class TypedefDecl(td: Typedef) extends WrapperTree - private case class FunDecl(f: Fun) extends WrapperTree - private case class FunSign(f: Fun) extends WrapperTree - private case class DeclParam(x: Var) extends WrapperTree - private case class EnumDef(u: Enum) extends WrapperTree - private case class TypeDef(t: Type) extends WrapperTree // This is not Typedef!!! - private case class StructDef(s: Struct) extends WrapperTree - private case class UnionDef(u: Union) extends WrapperTree - private case class UnionValueDef(typ: Type, id: Id) extends WrapperTree - private case object NewLine extends WrapperTree -} - diff --git a/src/main/scala/leon/genc/CPrinterHelper.scala b/src/main/scala/leon/genc/CPrinterHelper.scala deleted file mode 100644 index a173aa24b..000000000 --- a/src/main/scala/leon/genc/CPrinterHelper.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import CAST.Tree - -/* Printer helpers adapted to C code generation */ - -case class PrinterContext( - indent: Int, - printer: CPrinter -) - -object CPrinterHelpers { - implicit class Printable(val f: PrinterContext => Any) extends AnyVal { - def print(ctx: PrinterContext) = f(ctx) - } - - implicit class PrinterHelper(val sc: StringContext) extends AnyVal { - def c(args: Any*)(implicit ctx: PrinterContext): Unit = { - val printer = ctx.printer - import printer.WrapperTree - val sb = printer.sb - - val strings = sc.parts.iterator - val expressions = args.iterator - - var extraInd = 0 - var firstElem = true - - while(strings.hasNext) { - val s = strings.next.stripMargin - - // Compute indentation - val start = s.lastIndexOf('\n') - if(start >= 0 || firstElem) { - var i = start + 1 - while(i < s.length && s(i) == ' ') { - i += 1 - } - extraInd = (i - start - 1) / 2 - } - - firstElem = false - - // Make sure new lines are also indented - sb.append(s.replaceAll("\n", "\n" + (" " * ctx.indent))) - - val nctx = ctx.copy(indent = ctx.indent + extraInd) - - if (expressions.hasNext) { - val e = expressions.next - - e match { - case ts: Seq[Any] => - nary(ts).print(nctx) - - case t: Tree => - printer.pp(t)(nctx) - - case wt: WrapperTree => - printer.pp(wt)(nctx) - - case p: Printable => - p.print(nctx) - - case e => - sb.append(e.toString) - } - } - } - } - } - - def nary(ls: Seq[Any], sep: String = ", ", opening: String = "", closing: String = ""): Printable = { - val (o, c) = if(ls.isEmpty) ("", "") else (opening, closing) - val strs = o +: List.fill(ls.size-1)(sep) :+ c - - implicit pctx: PrinterContext => - new StringContext(strs: _*).c(ls: _*) - } - -} - - diff --git a/src/main/scala/leon/genc/ExtraOps.scala b/src/main/scala/leon/genc/ExtraOps.scala deleted file mode 100644 index 4d11a3463..000000000 --- a/src/main/scala/leon/genc/ExtraOps.scala +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import purescala.Definitions._ -import purescala.Types._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -private[genc] object ExtraOps { - - // Extra tools on FunDef, especially for annotations - implicit class FunDefOps(val fd: FunDef) { - def isMain = fd.id.name == "main" - - def isExtern = hasAnnotation("extern") - def isDropped = hasAnnotation("cCode.drop") - def isManuallyDefined = hasAnnotation(manualDefAnnotation) - - def getManualDefinition = { - assert(isManuallyDefined) - - val Seq(Some(code0), includesOpt0) = fd.extAnnotations(manualDefAnnotation) - val code = code0.asInstanceOf[String] - val includes0 = includesOpt0 map { _.asInstanceOf[String] } getOrElse "" - - val includes = - if (includes0.isEmpty) Nil - else { includes0 split ':' }.toSeq - - ManualDef(code, includes) - } - - case class ManualDef(code: String, includes: Seq[String]) - - private def hasAnnotation(annot: String) = fd.annotations contains annot - private val manualDefAnnotation = "cCode.function" - } - - // Extra tools on ClassDef, especially for annotations - implicit class ClassDefOps(val cd: ClassDef) { - def isManuallyTyped = hasAnnotation(manualTypeAnnotation) - def isDropped = hasAnnotation(droppedAnnotation) - - def getManualType = { - assert(isManuallyTyped) - - val Seq(Some(alias0), includesOpt0) = cd.extAnnotations(manualTypeAnnotation) - val alias = alias0.asInstanceOf[String] - val include = includesOpt0 map { _.asInstanceOf[String] } getOrElse "" - - ManualType(alias, include) - } - - case class ManualType(alias: String, include: String) - - def getTopParent: ClassDef = { - cd.parent map { case AbstractClassType(acd, _) => acd.getTopParent } getOrElse { cd } - } - - def isCandidateForInheritance = cd.isAbstract || cd.hasParent - - private def hasAnnotation(annot: String) = cd.annotations contains annot - private val manualTypeAnnotation = "cCode.typedef" - private val droppedAnnotation = "cCode.drop" - } -} - diff --git a/src/main/scala/leon/genc/GenerateCPhase.scala b/src/main/scala/leon/genc/GenerateCPhase.scala deleted file mode 100644 index 2d66adbc9..000000000 --- a/src/main/scala/leon/genc/GenerateCPhase.scala +++ /dev/null @@ -1,20 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import purescala.Definitions.Program - -object GenerateCPhase extends SimpleLeonPhase[Program, CAST.Prog] { - - val name = "Generate C" - val description = "Generate equivalent C code from Leon's AST" - - def apply(ctx: LeonContext, program: Program) = { - ctx.reporter.debug("Running code conversion phase: " + name)(utils.DebugSectionLeon) - val cprogram = new CConverter(ctx, program).convert - cprogram - } - -} - diff --git a/src/main/scala/leon/genc/MiniReporter.scala b/src/main/scala/leon/genc/MiniReporter.scala deleted file mode 100644 index 7e0d588e4..000000000 --- a/src/main/scala/leon/genc/MiniReporter.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -private[genc] trait MiniReporter { - - val ctx: LeonContext - - def internalError(msg: String) = { - import java.lang.Thread - - val stack = Thread.currentThread.getStackTrace - - debug(s"internal error `$msg` from:") - for (s <- stack) - debug(s.toString) - - ctx.reporter.internalError(msg) - } - - def fatalError(msg: String) = ctx.reporter.fatalError(msg) - - def debug(msg: String) = ctx.reporter.debug(msg)(utils.DebugSectionGenC) - - def debug(e: Throwable) { - import java.io.{ StringWriter, PrintWriter } - - val sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - - val stack = sw.toString(); - - debug(e.getMessage) - debug("Exception's stack trace:\n " + stack) - - val cause = e.getCause - if (cause != null) { - debug("because of") - debug(cause) - } - } - -} - diff --git a/src/main/scala/leon/genc/Normaliser.scala b/src/main/scala/leon/genc/Normaliser.scala deleted file mode 100644 index 406ec4347..000000000 --- a/src/main/scala/leon/genc/Normaliser.scala +++ /dev/null @@ -1,76 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import purescala.Expressions._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -private[genc] trait Normaliser { - this: CConverter => - - // Flattened represents a non-empty statement { a; b; ...; y; z } - // split into body { a; b; ...; y } and value z - case class Flattened(value: CAST.Stmt, body: Seq[CAST.Stmt]) - - // FlattenedSeq does the same as Flattened for a sequence of non-empty statements - case class FlattenedSeq(values: Seq[CAST.Stmt], bodies: Seq[CAST.Stmt]) - - def flatten(stmt: CAST.Stmt) = stmt match { - case CAST.Compound(stmts) if stmts.isEmpty => internalError(s"Empty compound cannot be flattened") - case CAST.Compound(stmts) => Flattened(stmts.last, stmts.init) - case stmt => Flattened(stmt, Seq()) - } - - def convertAndFlatten(expr: Expr)(implicit funCtx: FunCtx) = flatten(convertToStmt(expr)) - - // Normalise execution order of, for example, function parameters; - // `types` represents the expected type of the corresponding values - // in case an intermediary variable needs to be created - def convertAndNormaliseExecution(exprs: Seq[Expr], types: Seq[CAST.Type])(implicit funCtx: FunCtx) = { - require(exprs.length == types.length) - normaliseExecution(exprs map convertToStmt, types) - } - - def normaliseExecution(typedStmts: Seq[(CAST.Stmt, CAST.Type)]): FlattenedSeq = - normaliseExecution(typedStmts map { _._1 }, typedStmts map { _._2 }) - - def normaliseExecution(stmts: Seq[CAST.Stmt], types: Seq[CAST.Type]): FlattenedSeq = { - require(stmts.length == types.length) - - // Create temporary variables if needed - val stmtsFs = stmts map flatten - val fs = (stmtsFs zip types) map { - case (f, _) if f.value.isPureValue => f - - case (f, typ) => - // Similarly to buildDeclInitVar: - val (tmp, body) = f.value match { - case CAST.IfElse(cond, thn, elze) => - val tmp = CAST.FreshVar(typ.removeConst, "normexec") - val decl = CAST.DeclVar(tmp) - val ifelse = buildIfElse(cond, injectAssign(tmp, thn), injectAssign(tmp, elze)) - val body = f.body ~~ decl ~ ifelse - - (tmp, body) - - case value => - val tmp = CAST.FreshVal(typ, "normexec") - val body = f.body ~~ CAST.DeclInitVar(tmp, f.value) - - (tmp, body) - } - - val value = CAST.AccessVar(tmp.id) - flatten(body ~ value) - } - - val empty = Seq[CAST.Stmt]() - val bodies = fs.foldLeft(empty){ _ ++ _.body } - val values = fs map { _.value } - - FlattenedSeq(values, bodies) - } - -} - diff --git a/src/main/scala/leon/genc/TypeAnalyser.scala b/src/main/scala/leon/genc/TypeAnalyser.scala deleted file mode 100644 index 4ccd8a2cb..000000000 --- a/src/main/scala/leon/genc/TypeAnalyser.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc - -import purescala.Definitions._ -import purescala.Types._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -import utils.Position - -import ExtraOps._ - -private[genc] trait TypeAnalyser { - this: CConverter => - - // TODO This might need to be generalised... - // - One problem is with typedefs: should the type be returnable or not? The user might - // need to specify it manually. - // - Another issue is with case class with mutable members; references will get broken - // (not supported at all ATM). - def containsArrayType(typ: TypeTree)(implicit pos: Position): Boolean = typ match { - case CharType => false - case Int32Type => false - case BooleanType => false - case UnitType => false - case StringType => false // NOTE this might change in the future - case IntegerType => CAST.unsupported(s"BigInt") - case ArrayType(_) => true - case TupleType(bases) => bases exists containsArrayType - - case CaseClassType(cd, _) => classContainsArrayType(cd) - case AbstractClassType(cd, _) => classContainsArrayType(cd) - - case _ => CAST.unsupported(s"Unexpected TypeTree '$typ': ${typ.getClass}") - } - - private def classContainsArrayType(cd: ClassDef)(implicit pos: Position): Boolean = { - if (cd.isDropped) - CAST.unsupported(s"Using a dropped type") - - // If a case class is manually typdef'd, consider it to be a "returnable" type - if (getTypedef(cd).isDefined) false - else cd.fields map { _.getType } exists containsArrayType - } - -} - diff --git a/src/main/scala/leon/genc/converters/ClassConverter.scala b/src/main/scala/leon/genc/converters/ClassConverter.scala deleted file mode 100644 index f4940f8e6..000000000 --- a/src/main/scala/leon/genc/converters/ClassConverter.scala +++ /dev/null @@ -1,209 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc -package converters - -import purescala.Definitions._ -import purescala.Expressions._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -import utils.Position - -import ExtraOps._ - -private[converters] trait ClassConverter { - this: Converters with Normaliser with Builder with MiniReporter => - - // This registery keeps track of the "top" C structure that represents the class hierarchy. - private var classRegistery = Map[CaseClassDef, CAST.Struct]() - - // Add the given set of ClassDef into the registery - private def registerFullHierarchy(top: CAST.Struct, set: Seq[CaseClassDef]) { - debug(s"Registering hierarchy with $top for ${set map { _.id }}") - - for (clazz <- set) - classRegistery = classRegistery + (clazz -> top) - } - - // Find the matching "top" C struct for a given class definition. If none exists, - // the definition needs to be processed through convertClass. - private def getTopStruct(cd: CaseClassDef): Option[CAST.Struct] = classRegistery.get(cd) - - // Register a hierarchy of class. - // - // - Find the top abstract class - // - List all concreate classes - // - Create a C enum with a value for each concreate class - // - Create a C struct for each child - // - Create a C struct with a union member having an entry for each concreate class - // - Register the enum, union & the structs to ProgConverter - // - Register the class hierarchy as well - // - Return the struct representing this class hierarchy - private def registerClassHierarchy(cd: ClassDef): CAST.Type = { - val top = cd.getTopParent - val id = convertToId(top.id) - - getType(id) getOrElse { - val children = top.knownCCDescendants - - debug(s"Registrering class hierarchy of ${cd.id}") - debug(s"Top = ${top.id}") - debug(s"Children = ${ children map { _.id } mkString ", " }") - - val childrenStructs = children map registerClass - - val name = id.name - val enumId = CAST.Id(s"tag_${name}_t") - - val enumValues = childrenStructs map { s => CAST.Enum.tagForType(s) } - val enumType = CAST.Enum(enumId, enumValues) - - val unionType = CAST.Union(CAST.Id(s"union_$name"), childrenStructs) - - val tag = CAST.Var(CAST.Id("tag"), enumType) - val union = CAST.Var(CAST.Id("value"), unionType) - - val typ = CAST.Struct(CAST.Id(name), tag :: union :: Nil) - - registerEnum(enumType) - registerType(unionType) - registerType(typ) - - registerFullHierarchy(typ, children) - - typ - } - } - - // Register a given class (if needed) after converting its data structure to a C one. - // NOTE it is okay to call this function more than once on the same class definition. - private def registerClass(cd: ClassDef): CAST.Type = { - implicit val ctx = FunCtx.empty - - val id = convertToId(cd.id) - - val typ = getType(id) - typ foreach { t => debug(s"$t is already defined") } - - typ getOrElse { - val fields = cd.fields map convertToVar - val typ = CAST.Struct(id, fields) - - registerType(typ) - typ - } - } - - // Convert a given class into a C structure; make some additional checks to - // restrict the input class to the supported set of features. - def convertClass(cd: ClassDef): CAST.Type = { - debug(s"Processing ${cd.id} with annotations: ${cd.annotations}") - - implicit val pos = cd.getPos - - if (cd.isManuallyTyped && cd.isDropped) - CAST.unsupported(s"${cd.id} cannot be both dropped and manually defined") - - if (cd.isDropped) { - debug(s"${cd.id} is dropped") - CAST.NoType - } else getTypedef(cd) getOrElse { - if (cd.isCaseObject) CAST.unsupported("Case Objects") - if (cd.tparams.length > 0) CAST.unsupported("Type Parameters") - if (cd.methods.length > 0) CAST.unsupported("Methods") // TODO is it? - - // Handle inheritance - if (cd.isCandidateForInheritance) registerClassHierarchy(cd) - else registerClass(cd) - } - } - - // Instanciate a given case class, taking into account the inheritance model - def instanciateCaseClass(typ: CaseClassDef, args1: Seq[Expr])(implicit funCtx: FunCtx): CAST.Stmt = { - def details(struct: CAST.Struct): (Seq[CAST.Stmt], CAST.StructInit) = { - val types = struct.fields map { _.typ } - val argsFs = convertAndNormaliseExecution(args1, types) - val fieldsIds = typ.fieldsIds map convertToId - val args = fieldsIds zip argsFs.values - - (argsFs.bodies, CAST.StructInit(args, struct)) - } - - def normalInstantiation: CAST.Stmt = { - val struct = convertToStruct(typ) - val (pre, act) = details(struct) - - pre ~~ act - } - - def abstractInstantiation(top: CAST.Struct): CAST.Stmt = { - // Here is an example of how such init might look like: - // struct T t = (struct T){ .tag = INT, .value.t1 = (struct TINT){ .x = 42 } }; - // - // We need to identify the tag and the value name first, - // then how to init the value properly. - - debug(s"Instantiating ${typ.id} with arguments $args1.") - - val dataStruct = getStruct(convertToId(typ.id)).get // if None, then internalError anyway - val tag = CAST.Enum.tagForType(dataStruct) - val value = CAST.Union.valuePathForType(dataStruct) - - debug(s"Concreate struct: $dataStruct") - debug(s"Tag: $tag, value: $value") - - val (pre, dataInit) = details(dataStruct) - val args = (CAST.Id("tag") -> tag) :: (value -> dataInit) :: Nil - - debug(s"dataInit: $dataInit") - - pre ~~ CAST.StructInit(args, top) - } - - getTopStruct(typ) match { - case None => normalInstantiation - case Some(top) => abstractInstantiation(top) - } - } - - // Convert the expr.isInstanceOf[cd] for types involving inheritance into the proper check - // of the tag value. - def convertIsInstanceOf(expr: Expr, cd: ClassDef)(implicit pos: Position, funCtx: FunCtx): CAST.Stmt = { - checksForInstanceOf(cd) - - val exprF = convertAndFlatten(expr) - - val dataStruct = getStruct(convertToId(cd.id)).get // if None, then internalError anyway - val tag = CAST.Enum.tagForType(dataStruct) - - val tagField = CAST.AccessField(exprF.value, CAST.Id("tag")) - - exprF.body ~~ buildBinOp(tagField, "==", tag) - } - - // The conversion of expr.asInstanceOf[cd] is rather straighforward: we simply access the proper value - // from the instance's union. - def convertAsInstanceOf(expr: Expr, cd: ClassDef)(implicit pos: Position, funCtx: FunCtx): CAST.Stmt = { - checksForInstanceOf(cd) - - val exprF = convertAndFlatten(expr) - - val dataStruct = getStruct(convertToId(cd.id)).get // if None, then internalError anyway - val valuePath = CAST.Union.valuePathForType(dataStruct) - - val valueField = CAST.AccessField(exprF.value, valuePath) - - exprF.body ~~ valueField - } - - private def checksForInstanceOf(cd: ClassDef)(implicit pos: Position) = { - if (!cd.isCandidateForInheritance) - CAST.unsupported(s"IsInstanceOf w/ ${cd.id} doesn't involve inheritance") - - if (cd.isAbstract) - CAST.unsupported(s"IsInstanceOf w/ ${cd.id} doesn't work on abstract types") - } - -} - diff --git a/src/main/scala/leon/genc/converters/Converters.scala b/src/main/scala/leon/genc/converters/Converters.scala deleted file mode 100644 index 6d33299a6..000000000 --- a/src/main/scala/leon/genc/converters/Converters.scala +++ /dev/null @@ -1,306 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc -package converters - -import purescala.Common._ -import purescala.Definitions._ -import purescala.Expressions._ -import purescala.ExprOps -import purescala.Types._ -import xlang.Expressions._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -import ExtraOps._ - -private[genc] trait Converters -extends GenericConverter with FunConverter with ClassConverter with ProgConverter { - this: CConverter => - - override def convert(tree: Tree)(implicit funCtx: FunCtx): CAST.Tree = { - implicit val pos = tree.getPos - - tree match { - /* ---------------------------------------------------------- Types ----- */ - case CharType => CAST.Char - case Int32Type => CAST.Int32 - case BooleanType => CAST.Bool - case UnitType => CAST.Void - - case StringType => CAST.String - - case IntegerType => CAST.unsupported(s"BigInt") - - case ArrayType(base) => - val array = CAST.Array(convertToType(base)) - getType(array.id) getOrElse { - registerType(array) - array - } - - case TupleType(bases) => - val tuple = CAST.Tuple(bases map convertToType) - getType(tuple.id) getOrElse { - registerType(tuple) - tuple - } - - case cd: ClassDef => convertClass(cd) - case CaseClassType(cd, _) => convertClass(cd) - case AbstractClassType(cd, _) => convertClass(cd) - - - /* ------------------------------------------------------- Literals ----- */ - case CharLiteral(c) => CAST.Literal(c) - case IntLiteral(v) => CAST.Literal(v) - case BooleanLiteral(b) => CAST.Literal(b) - case UnitLiteral() => CAST.Literal(()) - case StringLiteral(s) => CAST.Literal(s) - - case InfiniteIntegerLiteral(_) => CAST.unsupported("BigInt") - - - /* ------------------------------------ Definitions and Statements ----- */ - case id: Identifier => CAST.Id(id.uniqueName) - - // Function parameter - case vd: ValDef => buildVal(vd.id, vd.getType) - - // Accessing variable - case v: Variable => buildAccessVar(v.id) - - case Block(exprs, last) => - // Interleave the "bodies" of flatten expressions and their values - // and generate a Compound statement - (exprs :+ last) map convertToStmt reduce { _ ~ _ } - - case Let(b, v, r) => buildLet(b, v, r, false) - case LetVar(b, v, r) => buildLet(b, v, r, true) - - case LetDef(fds, rest) => - fds foreach convertToFun // The functions get registered there - convertToStmt(rest) - - case Assignment(varId, expr) => - val f = convertAndFlatten(expr) - val x = buildAccessVar(varId) - - val assign = CAST.Assign(x, f.value) - - f.body ~~ assign - - case tuple @ Tuple(exprs) => - val struct = convertToStruct(tuple.getType) - val types = struct.fields map { _.typ } - val fs = convertAndNormaliseExecution(exprs, types) - val args = fs.values.zipWithIndex map { - case (arg, idx) => (CAST.Tuple.getNthId(idx + 1), arg) - } - - fs.bodies ~~ CAST.StructInit(args, struct) - - case TupleSelect(tuple1, idx) => // here idx is already 1-based - val struct = convertToStruct(tuple1.getType) - val tuple2 = convertToStmt(tuple1) - - val fs = normaliseExecution((tuple2, struct) :: Nil) - - val tuple = fs.values.head - - fs.bodies ~~ CAST.AccessField(tuple, CAST.Tuple.getNthId(idx)) - - case ArrayLength(array1) => - val array2 = convertToStmt(array1) - val arrayType = convertToType(array1.getType) - - val fs = normaliseExecution((array2, arrayType) :: Nil) - - val array = fs.values.head - - fs.bodies ~~ CAST.AccessField(array, CAST.Array.lengthId) - - case ArraySelect(array1, index1) => - val array2 = convertToStmt(array1) - val arrayType = convertToType(array1.getType) - val index2 = convertToStmt(index1) - - val fs = normaliseExecution((array2, arrayType) :: (index2, CAST.Int32) :: Nil) - - val array = fs.values(0) - val index = fs.values(1) - val ptr = CAST.AccessField(array, CAST.Array.dataId) - val select = CAST.SubscriptOp(ptr, index) - - fs.bodies ~~ select - - case NonemptyArray(elems, Some((value1, length1))) if elems.isEmpty => - val length2 = convertToStmt(length1) - val valueType = convertToType(value1.getType) - val value2 = convertToStmt(value1) - - val fs = normaliseExecution((length2, CAST.Int32) :: (value2, valueType) :: Nil) - val length = fs.values(0) - val value = fs.values(1) - - fs.bodies ~~ CAST.ArrayInit(length, valueType, value) - - case NonemptyArray(elems, Some(_)) => - CAST.unsupported("NonemptyArray with non empty elements") - - case NonemptyArray(elems, None) => // Here elems is non-empty - // Sort values according the the key (aka index) - val indexes = elems.keySet.toSeq.sorted - val values = indexes map { elems(_) } - - // Assert all types are the same - val types = values map { e => convertToType(e.getType) } - val typ = types(0) - val allSame = types forall { _ == typ } - if (!allSame) CAST.unsupported("Heterogenous arrays") - - val fs = convertAndNormaliseExecution(values, types) - - fs.bodies ~~ CAST.ArrayInitWithValues(typ, fs.values) - - case ArrayUpdate(array1, index1, newValue1) => - val array2 = convertToStmt(array1) - val index2 = convertToStmt(index1) - val newValue2 = convertToStmt(newValue1) - val values = array2 :: index2 :: newValue2 :: Nil - - val arePure = values forall { _.isPure } - val areValues = array2.isValue && index2.isValue // no newValue here - - newValue2 match { - case CAST.IfElse(cond, thn, elze) if arePure && areValues => - val array = array2 - val index = index2 - val ptr = CAST.AccessField(array, CAST.Array.dataId) - val select = CAST.SubscriptOp(ptr, index) - - val ifelse = buildIfElse(cond, injectAssign(select, thn), - injectAssign(select, elze)) - - ifelse - - case _ => - val arrayType = convertToType(array1.getType) - val indexType = CAST.Int32 - val valueType = convertToType(newValue1.getType) - val types = arrayType :: indexType :: valueType :: Nil - - val fs = normaliseExecution(values, types) - - val array = fs.values(0) - val index = fs.values(1) - val newValue = fs.values(2) - - val ptr = CAST.AccessField(array, CAST.Array.dataId) - val select = CAST.SubscriptOp(ptr, index) - val assign = CAST.Assign(select, newValue) - - fs.bodies ~~ assign - } - - case CaseClass(typ, args) => - debug(s"CaseClass($typ, $args)") - instanciateCaseClass(typ.classDef, args) - - case CaseClassSelector(_, x1, fieldId) => - val struct = convertToStruct(x1.getType) - val x2 = convertToStmt(x1) - - val fs = normaliseExecution((x2, struct) :: Nil) - val x = fs.values.head - - fs.bodies ~~ CAST.AccessField(x, convertToId(fieldId)) - - case LessThan(lhs, rhs) => buildBinOp(lhs, "<", rhs) - case GreaterThan(lhs, rhs) => buildBinOp(lhs, ">", rhs) - case LessEquals(lhs, rhs) => buildBinOp(lhs, "<=", rhs) - case GreaterEquals(lhs, rhs) => buildBinOp(lhs, ">=", rhs) - case Equals(lhs, rhs) => buildBinOp(lhs, "==", rhs) - - case Not(rhs) => buildUnOp ( "!", rhs) - - case And(exprs) => buildMultiOp("&&", exprs) - case Or(exprs) => buildMultiOp("||", exprs) - - case BVPlus(lhs, rhs) => buildBinOp(lhs, "+", rhs) - case BVMinus(lhs, rhs) => buildBinOp(lhs, "-", rhs) - case BVUMinus(rhs) => buildUnOp ( "-", rhs) - case BVTimes(lhs, rhs) => buildBinOp(lhs, "*", rhs) - case BVDivision(lhs, rhs) => buildBinOp(lhs, "/", rhs) - case BVRemainder(lhs, rhs) => buildBinOp(lhs, "%", rhs) - case BVNot(rhs) => buildUnOp ( "~", rhs) - case BVAnd(lhs, rhs) => buildBinOp(lhs, "&", rhs) - case BVOr(lhs, rhs) => buildBinOp(lhs, "|", rhs) - case BVXOr(lhs, rhs) => buildBinOp(lhs, "^", rhs) - case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, "<<", rhs) - case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, ">>", rhs) - case BVLShiftRight(lhs, rhs) => CAST.unsupported("Operator >>>") - - // Ignore assertions for now - case Ensuring(body, _) => convert(body) - case Require(_, body) => convert(body) - case Assert(_, _, body) => convert(body) - - case IfExpr(cond1, thn1, elze1) => - val condF = convertAndFlatten(cond1) - val thn = convertToStmt(thn1) - val elze = convertToStmt(elze1) - - condF.body ~~ buildIfElse(condF.value, thn, elze) - - case While(cond1, body1) => - val cond = convertToStmt(cond1) - val body = convertToStmt(body1) - - if (cond.isPureValue) CAST.While(cond, body) - else { - // Transform while (cond) { body } into - // while (true) { if (cond) { body } else { break } } - val condF = flatten(cond) - val ifelse = condF.body ~~ buildIfElse(condF.value, CAST.NoStmt, CAST.Break) - CAST.While(CAST.True, ifelse ~ body) - } - - case FunctionInvocation(tfd @ TypedFunDef(fd, _), stdArgs) => - // Make sure fd is not annotated with cCode.drop - if (fd.annotations contains "cCode.drop") { - CAST.unsupported(s"Calling a function annoted with @cCode.drop") - } - - // Make sure the called function will be defined at some point - collectIfNeeded(fd) - - // In addition to regular function parameters, add the callee's extra parameters - val id = convertToId(fd.id) - val types = tfd.params map { p => convertToType(p.getType) } - val fs = convertAndNormaliseExecution(stdArgs, types) - val extraArgs = funCtx.toArgs(getFunExtraArgs(id)) - val args = extraArgs ++ fs.values - - fs.bodies ~~ CAST.Call(id, args) - - - case _: StringConcat => CAST.unsupported("String manipulations") - - case m: MatchExpr => - val rewrite = ExprOps.matchToIfThenElse(m) - convert(rewrite) - - case IsInstanceOf(expr, ct) => convertIsInstanceOf(expr, ct.classDef) - case AsInstanceOf(expr, ct) => convertAsInstanceOf(expr, ct.classDef) - - case e @ Error(typ, desc) => - debug(s"WARNING: `$e` is currently ignored") - CAST.NoStmt - - case unsupported => - CAST.unsupported(s"$unsupported (of type ${unsupported.getClass})") - } - } -} - diff --git a/src/main/scala/leon/genc/converters/FunConverter.scala b/src/main/scala/leon/genc/converters/FunConverter.scala deleted file mode 100644 index 199374958..000000000 --- a/src/main/scala/leon/genc/converters/FunConverter.scala +++ /dev/null @@ -1,183 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc -package converters - -import purescala.Common._ -import purescala.Definitions._ -import purescala.Types._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -import utils.Position - -import ExtraOps._ - -private[converters] trait FunConverter { - this: Converters with TypeAnalyser with Builder with MiniReporter => - - // Extra information about inner functions' context - // See classes VarInfo and FunCtx and functions convertToFun and - // FunctionInvocation conversion - private var funExtraArgss = Map[CAST.Id, Seq[CAST.Id]]() - - // Register extra function argument for the function named `id` - private def registerFunExtraArgs(id: CAST.Id, params: Seq[CAST.Id]) { - funExtraArgss = funExtraArgss + ((id, params)) - } - - // Get the extra argument identifiers for the function named `id` - def getFunExtraArgs(id: CAST.Id) = funExtraArgss.getOrElse(id, Seq()) - - - // A variable can be locally declared (e.g. function parameter or local variable) - // or it can be "inherited" from a more global context (e.g. inner functions have - // access to their outer function parameters). - case class VarInfo(x: CAST.Var, local: Boolean) { - // Transform a local variable into a global variable - def lift = VarInfo(x, false) - - // Generate CAST variable declaration for function signature - def toParam = CAST.Var(x.id, CAST.Pointer(x.typ)) - - // Generate CAST access statement - def toArg = if (local) CAST.AccessAddr(x.id) else CAST.AccessVar(x.id) - } - - object FunCtx { - val empty = FunCtx(Seq()) - } - - case class FunCtx(vars: Seq[VarInfo]) { - // Transform local variables into "outer" variables - def lift = FunCtx(vars map { _.lift }) - - // Create a new context with more local variables - def extend(x: CAST.Var): FunCtx = extend(Seq(x)) - def extend(xs: Seq[CAST.Var]): FunCtx = { - val newVars = xs map { VarInfo(_, true) } - FunCtx(vars ++ newVars) - } - - // Check if a given variable's identifier exists in the context and is an "outer" variable - def hasOuterVar(id: CAST.Id) = vars exists { vi => !vi.local && vi.x.id == id } - - // List all variables' ids - def extractIds = vars map { _.x.id } - - // Generate arguments for the given identifiers according to the current context - def toArgs(ids: Seq[CAST.Id]) = { - val filtered = vars filter { ids contains _.x.id } - filtered map { _.toArg } - } - - // Generate parameters (var + type) - def toParams = vars map { _.toParam } - - // Check whether this context is empy or not - // i.e. if the function being extracted is a top level one - def isEmpty = vars.isEmpty - } - - // Extract inner functions too - def convertToFun(fd: FunDef)(implicit funCtx: FunCtx): Option[CAST.Fun] = { - implicit val pos = fd.getPos - - debug(s"Converting function ${fd.id.uniqueName} with annotations: ${fd.annotations}") - - if (!fd.isMain && fd.isExtern && !fd.isManuallyDefined && !fd.isDropped) - CAST.unsupported("Extern function need to be either manually defined or dropped") - - if (fd.isManuallyDefined && fd.isDropped) - CAST.unsupported("Function cannot be dropped and manually implemented at the same time") - - if (fd.isDropped) None - else { - // Special case: the `main(args)` function is actually just a proxy for `_main()` - val fun = - if (fd.isMain) convertToFun_main(fd) - else convertToFun_normal(fd) - - registerFun(fun) - - Some(fun) - } - } - - private def convertToFun_main(fd: FunDef)(implicit funCtx: FunCtx, pos: Position): CAST.Fun = { - if (!fd.isExtern) - CAST.unsupported("It is expected for `main(args)` to be extern") - - // Make sure there is a `_main()` function and has the proper signature - val uOpt = prog.units find { _ containsDef fd } - val u = uOpt getOrElse { internalError(s"FunDef comes from an unexpected place") } - val _mainFdOpt = u.definedFunctions find { _.id.name == "_main" } - if (_mainFdOpt.isEmpty) - CAST.unsupported("Please provide a _main() function") - - val _mainFd = _mainFdOpt.get - if (_mainFd.params.size > 0) - CAST.unsupported("_main() should not have parameters") - - // TODO Check for main overload and reject the program in such case - - // Artificially create the function (since it is tagged @extern) - val is_mainIntegral = _mainFd.returnType == Int32Type - CAST.generateMain(convertToId(_mainFd.id), is_mainIntegral) - } - - private def convertToFun_normal(fd: FunDef)(implicit funCtx: FunCtx, pos: Position): CAST.Fun = { - // Forbid return of array as they are allocated on the stack - if (containsArrayType(fd.returnType)) - CAST.unsupported("Returning arrays") - - // Extract basic information - val id = convertToId(fd.id) - val retType = convertToType(fd.returnType) - val stdParams = fd.params map convertToVar - - // Prepend existing variables from the outer function context to - // this function's parameters - val extraParams = funCtx.toParams - val params = extraParams ++ stdParams - - // Two main cases to handle for body extraction: - // - either the function is defined in Scala, or - // - the user provided a C code to use instead - - val body = if (fd.isManuallyDefined) { - if (!funCtx.isEmpty) - CAST.unsupported(s"Manual implementation cannot be specified for nested functions") - - val manualDef = fd.getManualDefinition - - // Register all the necessary includes - manualDef.includes foreach { i => registerInclude(CAST.Include(i)) } - - val body = manualDef.code.replaceAllLiterally("__FUNCTION__", id.name) - - Right(body.stripMargin) - } else { - // Function Context: - // 1) Save the variables of the current context for later function invocation - // 2) Lift & augment funCtx with the current function's arguments - // 3) Propagate it to the current function's body - - registerFunExtraArgs(id, funCtx.extractIds) - - val funCtx2 = funCtx.lift.extend(stdParams) - - val b = convertToStmt(fd.fullBody)(funCtx2) - val body = retType match { - case CAST.Void => b - case _ => injectReturn(b) - } - - Left(body) - } - - CAST.Fun(id, retType, params, body) - } - -} - diff --git a/src/main/scala/leon/genc/converters/GenericConverter.scala b/src/main/scala/leon/genc/converters/GenericConverter.scala deleted file mode 100644 index b9bccf5eb..000000000 --- a/src/main/scala/leon/genc/converters/GenericConverter.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc -package converters - -import purescala.Common._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -import scala.reflect.ClassTag - -private[converters] trait GenericConverter { - this: Converters with MiniReporter => - - // Apply the conversion function and make sure the resulting AST matches our expectation - def convertTo[T](tree: Tree)(implicit funCtx: FunCtx, ct: ClassTag[T]): T = convert(tree) match { - case t: T => t - case x => internalError(s"Expected an instance of $ct when converting $tree but got $x") - } - - // Generic conversion functions - // Currently simple aliases in case we need later to have special treatment instead - def convertToType (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Type](tree) - def convertToStruct(tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Struct](tree) - def convertToStmt (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Stmt](tree) - def convertToVar (tree: Tree)(implicit funCtx: FunCtx) = convertTo[CAST.Var](tree) - - // No need of FunCtx for identifiers - def convertToId(tree: Tree) = { - implicit val ctx = FunCtx.empty - convertTo[CAST.Id](tree) - } - - // This is the main conversion function, defined in Converters - def convert(tree: Tree)(implicit funCtx: FunCtx): CAST.Tree - -} - diff --git a/src/main/scala/leon/genc/converters/ProgConverter.scala b/src/main/scala/leon/genc/converters/ProgConverter.scala deleted file mode 100644 index 71c6fbe00..000000000 --- a/src/main/scala/leon/genc/converters/ProgConverter.scala +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon -package genc -package converters - -import purescala.Common._ -import purescala.Definitions._ -// NOTE don't import CAST._ to decrease possible confusion between the two ASTs - -private[converters] trait ProgConverter { - this: Converters with MiniReporter => - - val prog: Program // the program to be converted - // This is needed as a "global" for the converters mechanism - // to work properly we punctually need to fetch some specific - // data from this program. - - // Initially, only the main unit is processed but if it has dependencies in other - // units, they will be processed as well (and their dependencies as well). However, - // due to the state of the converter (e.g. function context) we don't do it recursively - // but iteratively until all required units have been processed. - // See markUnitForProcessing and processRequiredUnits. - private var unitsToProcess = Set[UnitDef]() - private var processedUnits = Set[UnitDef]() - - // Global data: keep track of the custom types and functions of the input program - // Using sequences and not sets to keep track of order/dependencies - private var typedefs = Seq[CAST.Typedef]() // typedef to standard types only - private var enums = Seq[CAST.Enum]() - private var types = Seq[CAST.TypeWithId]() // both structs and unions - private var functions = Seq[CAST.Fun]() - // Includes don't need specific orders, hence we use a set - private var includes = Set[CAST.Include]() // for manually defined functions - - - def registerInclude(incl: CAST.Include) { - includes = includes + incl - } - - def registerTypedef(typedef: CAST.Typedef) { - if (!typedefs.contains(typedef)) { - typedefs = typedefs :+ typedef - } - } - - // Return the manual C typedef contained in the class annotation, if any. - def getTypedef(cd: ClassDef): Option[CAST.Typedef] = { - import ExtraOps._ - - if (cd.isManuallyTyped) { - val manualType = cd.getManualType - val typedef = CAST.Typedef(convertToId(cd.id), CAST.Id(manualType.alias)) - - if (!manualType.include.isEmpty) - registerInclude(CAST.Include(manualType.include)) - - registerTypedef(typedef) - - Some(typedef) - } else None - } - - def registerEnum(enum: CAST.Enum) { - debug(s"Registering enum $enum") - enums = enums :+ enum - } - - def registerType(typ: CAST.TypeWithId) { - debug(s"Registering type $typ") - - if (getType(typ.id).isDefined) - internalError(s"Type $typ redefined") - - types = types :+ typ - } - - def getType(id: CAST.Id): Option[CAST.Type] = types find { _.id == id } - - def getStruct(id: CAST.Id): Option[CAST.Struct] = getType(id) match { - case Some(s: CAST.Struct) => Some(s) - case Some(x) => internalError(s"$id matches something $x: ${x.getClass} and not a CAST.Struct") - case None => None - } - - def registerFun(fun: CAST.Fun) { - // Functions should not get defined multiple times as this - // would imply invalidating funExtraArgss - if (functions contains fun) - internalError(s"Function ${fun.id} defined more than once") - else - functions = functions :+ fun - } - - // Mark a given unit as dependency - def markUnitForProcessing(unit: UnitDef) { - if (!processedUnits.contains(unit)) { - unitsToProcess = unitsToProcess + unit - } - } - - def collectIfNeeded(fd: FunDef) { - val funName = fd.id.uniqueName - if (!functions.find{ _.id.name == funName }.isDefined) { - val uOpt = prog.units find { _ containsDef fd } - val u = uOpt getOrElse { internalError(s"Function $funName was defined nowhere!") } - - debug(s"\t$funName is define in unit ${u.id}") - - markUnitForProcessing(u) - } - } - - def convertToProg: CAST.Prog = { - // Print some debug information about the program's units - val unitNames = prog.units map { u => (if (u.isMainUnit) "*" else "") + u.id } - debug(s"Input units are: " + unitNames.mkString(", ")) - - val mainUnits = prog.units filter { _.isMainUnit } - - if (mainUnits.size == 0) fatalError("No main unit in the program") - if (mainUnits.size >= 2) fatalError("Multiple main units in the program") - - val mainUnit = mainUnits.head - - // Start by processing the main unit - // Additional units are processed only if a function from the unit is used - markUnitForProcessing(mainUnit) - processRequiredUnits() - - CAST.Prog(includes, typedefs, enums, types, functions) - } - - // Process units until dependency list is empty - private def processRequiredUnits() { - while (!unitsToProcess.isEmpty) { - // Take any unit from the dependency list - val unit = unitsToProcess.head - unitsToProcess = unitsToProcess - unit - - // Mark it as processed before processing it to prevent infinite loop - processedUnits = processedUnits + unit - collectSymbols(unit) - } - } - - // Look for function and structure definitions - private def collectSymbols(unit: UnitDef) { - debug(s"Converting unit ${unit.id} which tree is:\n$unit") - - implicit val defaultFunCtx = FunCtx.empty - - // Note that functions, type declarations or typedefs are registered in convertTo* - unit.defs foreach { - case ModuleDef(id, defs, _) => - defs foreach { - case fd: FunDef => convertToFun(fd) - - case cd: ClassDef => convertToType(cd) - - case x => - implicit val pos = x.getPos - val prefix = s"[unit = ${unit.id}, module = $id]" - CAST.unsupported(s"$prefix Unexpected definition $x: ${x.getClass}") - } - - case cc: CaseClassDef => convertToType(cc) - - case x => - implicit val pos = x.getPos - val prefix = s"[unit = ${unit.id}]" - CAST.unsupported(s"$prefix Unexpected definition $x: ${x.getClass}") - } - } - -} - From bf7d3b1d047ce8e673994105320eca8be68272fe Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 1 Nov 2016 22:01:46 +0100 Subject: [PATCH 04/77] Add TimedLeonPhase trait --- src/main/scala/leon/LeonPhase.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/scala/leon/LeonPhase.scala b/src/main/scala/leon/LeonPhase.scala index 8fb530795..8eb500efd 100644 --- a/src/main/scala/leon/LeonPhase.scala +++ b/src/main/scala/leon/LeonPhase.scala @@ -14,6 +14,21 @@ trait SimpleLeonPhase[-F, +T] extends LeonPhase[F, T] { def run(ctx: LeonContext, v: F): (LeonContext, T) = (ctx, apply(ctx, v)) } +trait TimedLeonPhase[-F, +T] extends SimpleLeonPhase[F, T] { + protected def getTimer(ctx: LeonContext): utils.TimerStorage + + override def run(ctx: LeonContext, v: F): (LeonContext, T) = { + val timer = getTimer(ctx) + timer.start + + val res = (ctx, apply(ctx, v)) + + timer.stop + + res + } +} + abstract class TransformationPhase extends LeonPhase[Program, Program] { def apply(ctx: LeonContext, p: Program): Program From d35c0d3720f2ac061f947aefc817ab46418068b1 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 19 Oct 2016 18:29:13 +0200 Subject: [PATCH 05/77] Implement RemoveVCPhase --- src/main/scala/leon/utils/RemoveVCPhase.scala | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/scala/leon/utils/RemoveVCPhase.scala diff --git a/src/main/scala/leon/utils/RemoveVCPhase.scala b/src/main/scala/leon/utils/RemoveVCPhase.scala new file mode 100644 index 000000000..f6ba256b8 --- /dev/null +++ b/src/main/scala/leon/utils/RemoveVCPhase.scala @@ -0,0 +1,55 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon.utils + +import leon.{ LeonContext, TransformationPhase } +import leon.purescala.Definitions.{ ClassDef, FunDef, Program } +import leon.purescala.DefOps.{ definitionReplacer, transformProgram } +import leon.purescala.Expressions.{ Assert, Ensuring, Expr, Require } +import leon.purescala.ExprOps.{ preMap } + +/* + * This phase removes all verification condition it can find in the given + * program, without mutating it! + * + * NOTE This phase expects methods to have been lifted first. + */ +object RemoveVCPhase extends TransformationPhase { + + val name = "Remove verification conditions" + val description = "Remove assertions, loop invariant pre & postconditions" + + def apply(ctx: LeonContext, p: Program): Program = { + ctx.reporter.debug("Running VC removal phase")(leon.utils.DebugSectionLeon) + val timer = ctx.timers.removeVC.start() + + val res = transformProgram(transformer, p) + + timer.stop() + res + } + + // This transformer will replace function definitions and their invocations with a new + // version without VC. + private lazy val transformer = definitionReplacer(funMapper, noClassMapper) + + private def funMapper(fd: FunDef): Option[FunDef] = Some(funMapperImpl(fd)) + + private def funMapperImpl(fd: FunDef): FunDef = { + val newFd = fd.duplicate() + newFd.fullBody = preMap(exprMapper, applyRec = true)(newFd.fullBody) + newFd + } + + // Actually removed VC expressions + private def exprMapper(expr: Expr): Option[Expr] = expr match { + case Require(_, body) => Some(body) + case Ensuring(body, _) => Some(body) + case Assert(_, _, body) => Some(body) + case _ => None + } + + // Because we assume methods were lifted from classes, we don't need to procees them. + private def noClassMapper(cd: ClassDef): Option[ClassDef] = None +} + From 0e280b19b471d3f020ec18a521e80aa4b161bd89 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 8 Nov 2016 18:02:24 +0100 Subject: [PATCH 06/77] Add GenC v2 This new implementation of GenC uses a pipelined approach: the translation of Scala code into C99 equivalent code is split into several phases that live in the `leon.genc.phases` package. The overall process is bases on several versions of an Intermediate Representation (IR) that is defined under `leon.genc.ir`. Additionally, the C AST was partially redesigned. This new implementation is supposed to be better than the previous version in terms of bugs but there remain a few areas for improvement: * The `ClassLifter` is slightly incomplete: class fields should be processed as well. * When converting nested generic functions, the Scala2IRPhase might not do a proper job. Both of these points are explained in the source code; see the corresponding TODOs. --- src/main/scala/leon/genc/CAST.scala | 284 ++++++++++ .../scala/leon/genc/CFileOutputPhase.scala | 58 ++ src/main/scala/leon/genc/CPrinter.scala | 238 ++++++++ src/main/scala/leon/genc/CPrinterHelper.scala | 90 +++ src/main/scala/leon/genc/GenerateCPhase.scala | 30 + src/main/scala/leon/genc/MiniReporter.scala | 92 +++ src/main/scala/leon/genc/ir/ClassLifter.scala | 66 +++ .../scala/leon/genc/ir/DependencyFinder.scala | 27 + src/main/scala/leon/genc/ir/IR.scala | 375 +++++++++++++ src/main/scala/leon/genc/ir/IRPrinter.scala | 121 ++++ src/main/scala/leon/genc/ir/Literals.scala | 61 ++ src/main/scala/leon/genc/ir/Normaliser.scala | 288 ++++++++++ src/main/scala/leon/genc/ir/Operators.scala | 70 +++ .../scala/leon/genc/ir/PrimitiveTypes.scala | 21 + .../scala/leon/genc/ir/Referentiator.scala | 140 +++++ src/main/scala/leon/genc/ir/Transformer.scala | 153 +++++ src/main/scala/leon/genc/ir/Visitor.scala | 132 +++++ .../phases/ComputeDependenciesPhase.scala | 160 ++++++ .../leon/genc/phases/ComputeFunCtxPhase.scala | 84 +++ .../scala/leon/genc/phases/ExtraOps.scala | 105 ++++ .../genc/phases/ExtractEntryPointPhase.scala | 81 +++ .../scala/leon/genc/phases/IR2CPhase.scala | 420 ++++++++++++++ .../scala/leon/genc/phases/LiftingPhase.scala | 35 ++ .../leon/genc/phases/NormalisationPhase.scala | 33 ++ .../leon/genc/phases/ReferencingPhase.scala | 37 ++ .../leon/genc/phases/Scala2IRPhase.scala | 524 ++++++++++++++++++ src/main/scala/leon/genc/phases/package.scala | 22 + src/test/scala/leon/genc/GenCSuite.scala | 6 +- 28 files changed, 3750 insertions(+), 3 deletions(-) create mode 100644 src/main/scala/leon/genc/CAST.scala create mode 100644 src/main/scala/leon/genc/CFileOutputPhase.scala create mode 100644 src/main/scala/leon/genc/CPrinter.scala create mode 100644 src/main/scala/leon/genc/CPrinterHelper.scala create mode 100644 src/main/scala/leon/genc/GenerateCPhase.scala create mode 100644 src/main/scala/leon/genc/MiniReporter.scala create mode 100644 src/main/scala/leon/genc/ir/ClassLifter.scala create mode 100644 src/main/scala/leon/genc/ir/DependencyFinder.scala create mode 100644 src/main/scala/leon/genc/ir/IR.scala create mode 100644 src/main/scala/leon/genc/ir/IRPrinter.scala create mode 100644 src/main/scala/leon/genc/ir/Literals.scala create mode 100644 src/main/scala/leon/genc/ir/Normaliser.scala create mode 100644 src/main/scala/leon/genc/ir/Operators.scala create mode 100644 src/main/scala/leon/genc/ir/PrimitiveTypes.scala create mode 100644 src/main/scala/leon/genc/ir/Referentiator.scala create mode 100644 src/main/scala/leon/genc/ir/Transformer.scala create mode 100644 src/main/scala/leon/genc/ir/Visitor.scala create mode 100644 src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala create mode 100644 src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala create mode 100644 src/main/scala/leon/genc/phases/ExtraOps.scala create mode 100644 src/main/scala/leon/genc/phases/ExtractEntryPointPhase.scala create mode 100644 src/main/scala/leon/genc/phases/IR2CPhase.scala create mode 100644 src/main/scala/leon/genc/phases/LiftingPhase.scala create mode 100644 src/main/scala/leon/genc/phases/NormalisationPhase.scala create mode 100644 src/main/scala/leon/genc/phases/ReferencingPhase.scala create mode 100644 src/main/scala/leon/genc/phases/Scala2IRPhase.scala create mode 100644 src/main/scala/leon/genc/phases/package.scala diff --git a/src/main/scala/leon/genc/CAST.scala b/src/main/scala/leon/genc/CAST.scala new file mode 100644 index 000000000..1ece0da09 --- /dev/null +++ b/src/main/scala/leon/genc/CAST.scala @@ -0,0 +1,284 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import ir.Operators._ +import ir.PrimitiveTypes._ +import ir.Literals._ + +/* + * Here are defined classes used to represent AST of C programs. + * + * NOTE on char and string: + * because the C character and string literals encoding sets are + * highly dependent on platforms and compilers, only basic single-byte + * characters from the ASCII set are supported at the moment. + * + * Details on such literals can be found in the C99 standard in §3.7, + * §6.4.4.4 and §6.4.5, and more. + * + * NOTE Because types in union shall not be partially defined we need to + * keep track of the dependencies between Struct's and Union's in a + * Prog's types. We do this lazily by requiring the `types` field to + * be sorted appropriately. Also, it shall only contains Struct's + * and Union's, no other kind of Type's. + */ +object CAST { // C Abstract Syntax Tree + + sealed abstract class Tree + + + /* ----------------------------------------------------- Definitions ----- */ + abstract class Def extends Tree + + case class Include(file: String) extends Def { + require(file.nonEmpty && isASCII(file)) + } + + case class Prog( + includes: Set[Include], + typedefs: Set[Typedef], + enums: Set[Enum], + types: Seq[DataType], // Both structs and unions, order IS important! See NOTE above. + functions: Set[Fun] + ) extends Def { + require(types.length == types.distinct.length) // no duplicates in `types` + } + + // Manually defined function through the cCode.function annotation have a string + // for signature+body instead of the usual Stmt AST exclusively for the body + case class Fun(id: Id, returnType: Type, params: Seq[Var], body: Either[Block, String]) extends Def + + case class Id(name: String) extends Def { + // TODO add check on name's domain for conformance + + // `|` is used as the margin delimiter and can cause trouble in some situations, + // for example when name start with a `|`. + def fixMargin = + if (name.size > 0 && name(0) == '|') "| " + name + else name + } + + case class Var(id: Id, typ: Type) extends Def + + + /* ------------------------------------------------------------ Types ----- */ + abstract class Type extends Tree + + abstract class DataType extends Type { + val id: Id + val fields: Seq[Var] + } + + case class Typedef(orig: Id, alias: Id) extends Type + case class Primitive(pt: PrimitiveType) extends Type + case class Pointer(base: Type) extends Type + + case class Struct(id: Id, fields: Seq[Var]) extends DataType { + require(fields.nonEmpty) + } + + case class Union(id: Id, fields: Seq[Var]) extends DataType { + require(fields.nonEmpty) + } + + case class Enum(id: Id, literals: Seq[EnumLiteral]) extends Type { + require(literals.nonEmpty) + } + + + /* ------------------------------------------------------ Expressions ----- */ + abstract class Expr extends Tree + + case class Block(exprs: Seq[Expr]) extends Expr // Can be empty + + case class Lit(lit: Literal) extends Expr + + case class EnumLiteral(id: Id) extends Expr + + case class Decl(id: Id, typ: Type) extends Expr + + case class DeclInit(id: Id, typ: Type, value: Expr) extends Expr { + require(value.isValue && !value.isReference) + } + + case class DeclArrayStatic(id: Id, base: Type, length: Int, values: Seq[Expr]) extends Expr { + require(values forall { _.isValue }) + } + + case class DeclArrayVLA(id: Id, base: Type, length: Expr, defaultExpr: Expr) extends Expr { + require( + length.isValue && + // length.getType == Primitive(Int32Type) && + defaultExpr.isValue + ) + } + + // Initialise all the fields of a struct, in the same order as they are declared. + case class StructInit(struct: Struct, values: Seq[Expr]) extends Expr { + require( + values.length == struct.fields.length && // Allow only explicit initialisation. + (values forall { _.isValue }) + ) + } + + // Initialise one of the fields of the union + case class UnionInit(union: Union, fieldId: Id, value: Expr) extends Expr { + require( + (union.fields exists { _.id == fieldId }) && // The requested field must exists. + value.isValue // && + // (union.fields forall { f => f.id != fieldId || f.getType == value.getType }) + ) + } + + case class Call(id: Id, args: Seq[Expr]) extends Expr { + require(args forall { _.isValue }) + } + + case class Binding(id: Id) extends Expr + + case class FieldAccess(objekt: Expr, fieldId: Id) extends Expr { + require( + objekt.isValue //&& + /* + * (objekt.getType match { + * case dt: DataType => dt.fields exists { _.id == fieldId } + * case _ => false + * }) + */ + ) + } + + case class ArrayAccess(array: Expr, index: Expr) extends Expr { + require( + array.isValue // && + // array.getType.isInstanceOf[ArrayType] + ) + } + + case class Ref(e: Expr) extends Expr { + require(e.isValue) + } + + case class Deref(e: Expr) extends Expr { + require(e.isValue) + } + + case class Assign(lhs: Expr, rhs: Expr) extends Expr { + require( + // lhs.getType == rhs.getType && + lhs.isValue && rhs.isValue + ) + } + + case class BinOp(op: BinaryOperator, lhs: Expr, rhs: Expr) extends Expr { + require(lhs.isValue && rhs.isValue) + } + + case class UnOp(op: UnaryOperator, expr: Expr) extends Expr { + require(expr.isValue) + } + + case class If(cond: Expr, thenn: Block) extends Expr { + require( + cond.isValue // && + // cond.getType == Primitive(BoolType) + ) + } + + case class IfElse(cond: Expr, thenn: Block, elze: Block) extends Expr { + require( + cond.isValue // && + // cond.getType == Primitive(BoolType) + ) + } + + case class While(cond: Expr, body: Block) extends Expr { + require( + cond.isValue // && + // cond.getType == Primitive(BoolType) + ) + } + + case object Break extends Expr + + case class Return(value: Expr) extends Expr { + require(value.isValue) + } + + + /* ---------------------------------------------------------- Helpers ----- */ + // Flatten blocks together and remove `()` literals + def buildBlock(exprs: Seq[Expr]): Block = { + val block = (Seq[Expr]() /: (exprs filterNot isUnitLit)) { + case (acc, e) => e match { + case Block(exprs) => acc ++ exprs + case expr => acc :+ expr + } + } + + Block(block) + } + + def buildBlock(expr: Expr): Block = buildBlock(Seq(expr)) + + def generateMain(returnInt: Boolean): Fun = { + val id = Id("main") + val retType = Primitive(Int32Type) + val argc = Var(Id("argc"), Primitive(Int32Type)) + val argv = Var(Id("argv"), Pointer(Pointer(Primitive(CharType)))) + val params = argc :: argv :: Nil + + val _mainId = Id("_main") + + val body = buildBlock( + if (returnInt) Return(Call(_mainId, Nil)) :: Nil + else Call(_mainId, Nil) :: Return(Lit(IntLit(0))) :: Nil + ) + + val main = Fun(id, retType, params, Left(body)) + + main + } + + object FreshId { + private val counter = new utils.UniqueCounter[String]() + + def apply(prefix: String): Id = { + val idx = counter.next(prefix) + Id("leon_" + prefix + "_" + idx) + } + } + + val True = Lit(BoolLit(true)) + + + /* ---------------------------------------------------------- Details ----- */ + // String & char limitations, see NOTE above + private def isASCII(c: Char): Boolean = { c >= 0 && c <= 127 } + private def isASCII(s: String): Boolean = s forall isASCII + + private def isUnitLit(e: Expr): Boolean = e match { + case Lit(UnitLit) => true + case _ => false + } + + + /* ---------------------------------------------- Sanitisation Helper ----- */ + private implicit class ExprValidation(e: Expr) { + def isValue = e match { + case _: Binding | _: Lit | _: EnumLiteral | _: StructInit | + _: UnionInit | _: Call | _: FieldAccess | _: ArrayAccess | + _: Ref | _: Deref | _: BinOp | _: UnOp => true + case _ => false + } + + def isReference = e match { + case _: Ref => true + case _ => false + } + } + +} + diff --git a/src/main/scala/leon/genc/CFileOutputPhase.scala b/src/main/scala/leon/genc/CFileOutputPhase.scala new file mode 100644 index 000000000..ea87029f5 --- /dev/null +++ b/src/main/scala/leon/genc/CFileOutputPhase.scala @@ -0,0 +1,58 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import java.io.File +import java.io.FileWriter +import java.io.BufferedWriter + +object CFileOutputPhase extends UnitPhase[CAST.Prog] { + + val name = "C File Output" + val description = "Output converted C program to the specified file (default leon.c)" + + val optOutputFile = new LeonOptionDef[String] { + val name = "o" + val description = "Output file" + val default = "leon.c" + val usageRhs = "file" + val parser = OptionParsers.stringParser + } + + override val definedOptions: Set[LeonOptionDef[Any]] = Set(optOutputFile) + + def apply(ctx: LeonContext, program: CAST.Prog) { + val timer = ctx.timers.genc.print.start() + + // Get the output file name from command line options, or use default + val outputFile = new File(ctx.findOptionOrDefault(optOutputFile)) + val parent = outputFile.getParentFile() + try { + if (parent != null) { + parent.mkdirs() + } + } catch { + case _ : java.io.IOException => ctx.reporter.fatalError("Could not create directory " + parent) + } + + // Output C code to the file + try { + val fstream = new FileWriter(outputFile) + val out = new BufferedWriter(fstream) + + val p = new CPrinter + p.print(program) + + out.write(p.toString) + out.close() + + ctx.reporter.info(s"Output written to $outputFile") + } catch { + case _ : java.io.IOException => ctx.reporter.fatalError("Could not write on " + outputFile) + } + + timer.stop() + } + +} diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala new file mode 100644 index 000000000..1296c1d49 --- /dev/null +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -0,0 +1,238 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import CAST._ +import CPrinterHelpers._ +import ir.PrimitiveTypes._ +import ir.Literals._ + +class CPrinter(val sb: StringBuffer = new StringBuffer) { + override def toString = sb.toString + + def print(tree: Tree) = pp(tree)(PrinterContext(indent = 0, printer = this, previous = None, current = tree)) + + private[genc] def pp(tree: Tree)(implicit ctx: PrinterContext): Unit = tree match { + case Prog(includes, typedefs0, enums0, types, functions0) => + // We need to convert Set to Seq in order to use nary. + val typedefs = typedefs0.toSeq + val enums = enums0.toSeq + val functions = functions0.toSeq + + c"""|/* ------------------------------------ includes ----- */ + | + |${nary(buildIncludes(includes), sep = "\n")} + | + |/* -------------------------------- type aliases ----- */ + | + |${nary(typedefs map TypedefDecl, sep = "\n")} + | + |/* --------------------------------------- enums ----- */ + | + |${nary(enums map EnumDef, sep = "\n\n")} + | + |/* ----------------------- data type definitions ----- */ + | + |${nary(types map TypeDef, sep = "\n\n")} + | + |/* ----------------------- function declarations ----- */ + | + |${nary(functions map FunDecl, sep = "\n")} + | + |/* ------------------------ function definitions ----- */ + | + |${nary(functions, sep = "\n\n")} + |""" + + // Manually defined function + case Fun(id, _, _, Right(function)) => + val fun = function.replaceAllLiterally("__FUNCTION__", id.name) + c"$fun" + + // Auto-generated function + case fun @ Fun(_, _, _, Left(body)) => + c"""|${FunSign(fun)} { + | $body + |}""" + + case Id(name) => c"$name" + + case Var(id, typ) => c"$typ $id" + + case Typedef(orig, _) => c"$orig" + + case Primitive(pt) => pt match { + case CharType => c"char" + case Int32Type => c"int32_t" + case BoolType => c"bool" + case UnitType => c"void" + case StringType => c"char*" + } + + case Pointer(base) => c"$base*" + + case Struct(id, _) => c"$id" + + case Union(id, _) => c"$id" + + case Enum(id, _) => c"$id" + + case Block(exprs) => + for { (e, i) <- exprs.zipWithIndex } { + c"$e" + e match { + case _: If | _: IfElse | _: While => () + case _ => c";" + } + if (i < exprs.length - 1) // new line, except for the last expression + c"""| + |""" + } + + case Lit(lit) => c"$lit" + + case EnumLiteral(lit) => c"$lit" + + case Decl(id, typ) => c"$typ $id" + + case DeclInit(id, typ, value) => c"$typ $id = $value" + + // TODO Visual "optimisation" can be made here if all values are zeros + case DeclArrayStatic(id, base, length, values) => + c"$base $id[$length] = { ${nary(values, sep = ", ")} }" + + case DeclArrayVLA(id, base, length, defaultExpr) => + val i = FreshId("i") + c"""|$base $id[$length]; + |${Decl(i, Primitive(Int32Type))}; + |for ($i = 0; $i < $length; ++$i) { + | $id[$i] = $defaultExpr; + |}""" + + case StructInit(struct, values) => + val args = struct.fields zip values + c"(${struct.id}) { ${nary(args map { case (Var(id, _), arg) => FieldInit(id, arg) }, sep = ", ")} }" + + case UnionInit(union, fieldId, value) => + c"(${union.id}) { ${FieldInit(fieldId, value)} }" + + case Call(id, args) => c"$id(${nary(args)})" + + case Binding(id) => c"$id" + + case FieldAccess(Deref(objekt), fieldId) => c"$objekt->$fieldId" + + case FieldAccess(objekt, fieldId) => c"$objekt.$fieldId" + + case ArrayAccess(array, index) => c"$array[$index]" + + case Ref(e) => c"&$e" + + case Deref(e) => optP { c"*$e" } + + case Assign(lhs, rhs) => c"$lhs = $rhs" + + case BinOp(op, lhs, rhs) => optP { c"$lhs $op $rhs" } + + case UnOp(op, expr) => optP { c"$op$expr" } + + case If(cond, thenn) => + c"""|if ($cond) { + | $thenn + |}""" + + case IfElse(cond, thenn, Block(Seq(secondIf @ If(_, _)))) => + c"""|if ($cond) { + | $thenn + |} else $secondIf""" + + case IfElse(cond, thenn, Block(Seq(secondIf @ IfElse(_, _, _)))) => + c"""|if ($cond) { + | $thenn + |} else $secondIf""" + + case IfElse(cond, thenn, elze) => + c"""|if ($cond) { + | $thenn + |} else { + | $elze + |}""" + + case While(cond, body) => + c"""|while ($cond) { + | $body + |}""" + + case Break => c"break" + + case Return(value) => c"return $value" + } + + private[genc] def pp(wt: WrapperTree)(implicit ctx: PrinterContext): Unit = wt match { + case FunSign(Fun(id, returnType, Seq(), _)) => c"$returnType $id(void)" + + case FunSign(Fun(id, returnType, params, _)) => c"$returnType $id(${nary(params)})" + + case FunDecl(f) => c"${FunSign(f)};" + + case TypedefDecl(Typedef(original, alias)) => c"typedef $alias $original;" + + case EnumDef(Enum(id, literals)) => + c"""|typedef enum { + | ${nary(literals, sep = ",\n")} + |} $id;""" + + case TypeDef(t: DataType) => + c"""|typedef struct { + | ${nary(t.fields, sep = ";\n", closing = ";")} + |} ${t.id};""" + + case FieldInit(id, value) => c".$id = $value" + } + + + /** Hardcoded list of required include files from C standard library **/ + private lazy val includes_ = Set("assert.h", "stdbool.h", "stdint.h") map Include + + private def buildIncludes(includes: Set[Include]): Seq[String] = + (includes_ ++ includes).toSeq sortBy { _.file } map { i => s"#include <${i.file}>" } + + + /** Wrappers to distinguish how the data should be printed **/ + private[genc] sealed abstract class WrapperTree + private case class FunSign(f: Fun) extends WrapperTree + private case class FunDecl(f: Fun) extends WrapperTree + private case class TypedefDecl(td: Typedef) extends WrapperTree + private case class EnumDef(u: Enum) extends WrapperTree + private case class TypeDef(t: DataType) extends WrapperTree // This is not Typedef!!! + private case class FieldInit(id: Id, value: Expr) extends WrapperTree + + + /** Special helpers for pretty parentheses **/ + private def optP(body: => Any)(implicit ctx: PrinterContext) = { + if (requiresParentheses(ctx.current, ctx.previous)) { + sb.append("(") + body + sb.append(")") + } else { + body + } + } + + private def requiresParentheses(current: Tree, previous: Option[Tree]): Boolean = (current, previous) match { + case (_, None) => false + case (_, Some(_: DeclInit | _: Call | _: ArrayAccess | _: If | _: IfElse | _: While | _: Return | _: Assign)) => false + case (Operator(precedence1), Some(Operator(precedence2))) if precedence1 <= precedence2 => false + case (_, _) => true + } + + private object Operator { + def unapply(tree: Tree): Option[Int] = tree match { + case BinOp(op, _, _) => Some(op.precedence) + case UnOp(op, _) => Some(op.precedence) + case _ => None + } + } +} + diff --git a/src/main/scala/leon/genc/CPrinterHelper.scala b/src/main/scala/leon/genc/CPrinterHelper.scala new file mode 100644 index 000000000..b7b9b69a8 --- /dev/null +++ b/src/main/scala/leon/genc/CPrinterHelper.scala @@ -0,0 +1,90 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import CAST.Tree + +/* Printer helpers adapted to C code generation */ + +case class PrinterContext( + indent: Int, + printer: CPrinter, + previous: Option[Tree], + current: Tree +) + +object CPrinterHelpers { + implicit class Printable(val f: PrinterContext => Any) extends AnyVal { + def print(ctx: PrinterContext) = f(ctx) + } + + implicit class PrinterHelper(val sc: StringContext) extends AnyVal { + def c(args: Any*)(implicit ctx: PrinterContext): Unit = { + val printer = ctx.printer + val sb = printer.sb + + import printer.WrapperTree + + val strings = sc.parts.iterator + val expressions = args.iterator + + var extraInd = 0 + var firstElem = true + + while(strings.hasNext) { + val s = strings.next.stripMargin + + // Compute indentation + val start = s.lastIndexOf('\n') + if(start >= 0 || firstElem) { + var i = start + 1 + while(i < s.length && s(i) == ' ') { + i += 1 + } + extraInd = (i - start - 1) / 2 + } + + firstElem = false + + // Make sure new lines are also indented + sb.append(s.replaceAll("\n", "\n" + (" " * ctx.indent))) + + val nctx = ctx.copy(indent = ctx.indent + extraInd) + + if (expressions.hasNext) { + val e = expressions.next + + e match { + case ts: Seq[Any] => + nary(ts).print(nctx) + + case t: Tree => + val nctx2 = nctx.copy(current = t, previous = Some(nctx.current)) + printer.pp(t)(nctx2) + + case wt: WrapperTree => + printer.pp(wt)(nctx) + + case p: Printable => + p.print(nctx) + + case e => + sb.append(e.toString) + } + } + } + } + } + + def nary(ls: Seq[Any], sep: String = ", ", opening: String = "", closing: String = ""): Printable = { + val (o, c) = if (ls.isEmpty) ("", "") else (opening, closing) + val strs = o +: List.fill(ls.size-1)(sep) :+ c + + implicit pctx: PrinterContext => + new StringContext(strs: _*).c(ls: _*) + } + +} + + diff --git a/src/main/scala/leon/genc/GenerateCPhase.scala b/src/main/scala/leon/genc/GenerateCPhase.scala new file mode 100644 index 000000000..c1189fdcb --- /dev/null +++ b/src/main/scala/leon/genc/GenerateCPhase.scala @@ -0,0 +1,30 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import purescala.Definitions.{ Program } +import utils.{ DebugSectionTrees, RemoveVCPhase } + +import phases._ + +object GenerateCPhase extends LeonPhase[Program, CAST.Prog] { + + val name = "Generate C" + val description = "Preprocess and convert Leon's AST to C" + + val pipeline = + RemoveVCPhase andThen + Pipeline.both(NoopPhase[Program], ExtractEntryPointPhase) andThen + ComputeDependenciesPhase andThen + Pipeline.both(NoopPhase[Dependencies], ComputeFunCtxPhase) andThen + Scala2IRPhase andThen + ReferencingPhase andThen + NormalisationPhase andThen + LiftingPhase andThen + IR2CPhase + + def run(ctx: LeonContext, prog: Program) = pipeline.run(ctx, prog) + +} + diff --git a/src/main/scala/leon/genc/MiniReporter.scala b/src/main/scala/leon/genc/MiniReporter.scala new file mode 100644 index 000000000..69c7fc565 --- /dev/null +++ b/src/main/scala/leon/genc/MiniReporter.scala @@ -0,0 +1,92 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import utils.{ NoPosition, Position } + +/* + * The MiniReporter trait is a simple convinient trait that provides + * overloading and shorthands for the usual reporter from LeonContext. + * + * The MiniReporter object simply provides a way to create an instance + * of the same trait. It is useful if one whish to "import" the shorthands + * into the current scope without using inheritance. E.g.: + * + * val reporter = MiniReporter(ctx) + * import reporter._ + */ + +private[genc] object MiniReporter { + def apply(ctx0: LeonContext) = new MiniReporter { val ctx = ctx0 } +} + +private[genc] trait MiniReporter { + + protected val ctx: LeonContext + + def internalError(msg: String, cause: Throwable = null) = { + import java.lang.Thread + + val stack = Thread.currentThread.getStackTrace + + debug(s"internal error `$msg` from:") + for (s <- stack) + debug(s.toString) + + if (cause != null) { + debug("because of") + debug(cause) + } + + ctx.reporter.internalError(msg) + } + + def fatalError(msg: String, pos: Position = NoPosition) = ctx.reporter.fatalError(pos, msg) + + def debugTree(title: String, tree: ir.IR#Tree) = { + if (ctx.reporter.isDebugEnabled(utils.DebugSectionTrees)) { + ctx.reporter.info("\n") + ctx.reporter.info(utils.ASCIIHelpers.title(title)) + ctx.reporter.info("\n") + ctx.reporter.info(tree.toString) + ctx.reporter.info("\n") + } + } + + def debug(msg: String) = ctx.reporter.debug(msg)(utils.DebugSectionGenC) + + def debug(e: Throwable) { + import java.io.{ StringWriter, PrintWriter } + + val msg = e.getMessage + if (msg != null) + debug(e.getMessage) + + val sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + + val stack = sw.toString(); + debug("Exception's stack trace:\n " + stack) + + val cause = e.getCause + if (cause != null) { + debug("because of") + debug(cause) + } + } + + def debug(msg: String, tree: CAST.Tree): Unit = debug(msg + ": " + tree2String(tree) + " of " + tree.getClass) + + def debug(tree: CAST.Tree): Unit = debug(tree2String(tree) + " of " + tree.getClass) + + private def tree2String(tree: CAST.Tree): String = { + val p = new CPrinter() + p.print(tree) + p.toString + } + + def warning(msg: String, pos: Position = NoPosition) = ctx.reporter.warning(pos, msg) + +} + diff --git a/src/main/scala/leon/genc/ir/ClassLifter.scala b/src/main/scala/leon/genc/ir/ClassLifter.scala new file mode 100644 index 000000000..2c0e8e153 --- /dev/null +++ b/src/main/scala/leon/genc/ir/ClassLifter.scala @@ -0,0 +1,66 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import IRs.{ NIR, LIR } + +// Lift object type to their top level type in order to properly use tagged union. +// TODO do the same for class fields!!! +// TODO use use env, maybe??? +final class ClassLifter(val ctx: LeonContext) extends Transformer(NIR, LIR) with MiniReporter { + import from._ + + type Mapping = Map[ValDef, Option[to.AsA]] + + class Env(val mapping: Mapping, val liftFlag: Boolean) { + def ++(other: Mapping) = new Env(mapping ++ other, liftFlag) + def ++(other: Env) = new Env(mapping ++ other.mapping, liftFlag || other.liftFlag) + + def dontLift = new Env(mapping, false) + } + + val Ø: Env = new Env(Map.empty, true) // lift type by default + + // Lift context, params & return type + override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { + val id = fd.id + + val returnType = rec(fd.returnType) + + val (ctx, ctxAccess) = (fd.ctx map lift).unzip + val (params, paramsAccess) = (fd.params map lift).unzip + + // Build our new environment + val newEnv = env ++ ((fd.ctx ++ fd.params) zip (ctxAccess ++ paramsAccess)).toMap + + // Handle recursive functions + val newer = to.FunDef(id, returnType, ctx, params, null) + registerFunction(fd, newer) + + newer.body = rec(fd.body)(newEnv) + + newer + } + + override def rec(typ: Type)(implicit env: Env): to.Type = super.rec(typ) match { + case to.ClassType(clazz) if env.liftFlag => to.ClassType(clazz.hierarchyTop) + case typ => typ + } + + // Lift only for classes, return (vd, None) for, e.g., primitive or array + private def lift(vd0: ValDef)(implicit env: Env): (to.ValDef, Option[to.AsA]) = { + val vd = rec(vd0) + + val typ = rec(vd0.getType)(env.dontLift) + val access = typ match { + case ct @ to.ClassType(_) => Some(to.AsA(to.Binding(vd), ct)) + case _ => None + } + + vd -> access + } + +} + diff --git a/src/main/scala/leon/genc/ir/DependencyFinder.scala b/src/main/scala/leon/genc/ir/DependencyFinder.scala new file mode 100644 index 000000000..a68967c2a --- /dev/null +++ b/src/main/scala/leon/genc/ir/DependencyFinder.scala @@ -0,0 +1,27 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import scala.collection.mutable.{ Set => MutableSet } + +/* + * Compute the dependencies of a program; that is, the set of functions and classes involved. + * + * NOTE Don't reuse an instance of this DependencyFinder on multiple trees! + */ +final class DependencyFinder[S <: IR](s: S) extends Visitor(s) { + import ir._ + + def getFunctions: Set[S#FunDef] = _funs.toSet + def getClasses: Set[S#ClassDef] = _classes.toSet + + private val _funs = MutableSet[S#FunDef]() + private val _classes = MutableSet[S#ClassDef]() + + override def visit(fd: FunDef): Unit = _funs += fd + override def visit(cd: ClassDef): Unit = _classes += cd + +} + diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala new file mode 100644 index 000000000..4f1e4fb08 --- /dev/null +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -0,0 +1,375 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +/* + * A relatively immutable Intermediary Representation for the Scala to C conversion. + * + * NOTE ClassDef is mutable: subclasses get registered automatically to their parent (and mutate it). + * this means, when processing a class, you have to process the whole hierarchy if you don't + * want to lose a class definition. See for example how it's done in Class.refine. + * + * NOTE FunDef is mutable: in order to support recursive (mutually) functions, we need to be able + * to build a partial version of a FunDef, without a body, for App constructions. This + * partial FunDef has a null body. + */ +private[genc] sealed trait IR { ir => + + // Add ability to pretty print the tree + val printer = new IRPrinter[ir.type](ir) + + type Id = String + type ClassHierarchy = Set[ClassDef] + + import PrimitiveTypes.{ PrimitiveType => PT, _ } // For desambiguation + import Literals._ + import Operators._ + + sealed abstract class Tree { + override def toString: String = prettyString(0) + + def prettyString(indent: Int): String = printer(this)(printer.Context(indent)) + } + + + /**************************************************************************************************** + * Definition Tree * + ****************************************************************************************************/ + sealed abstract class Def extends Tree + + case class ProgDef(entryPoint: FunDef) extends Def { + require( + entryPoint.id == "_main" + ) + } + + // Define a function body as either a regular AST or a manually defined + // function using @cCode.function + sealed abstract class FunBody + case class FunBodyAST(body: Expr) extends FunBody + case class FunBodyManual(includes: Seq[String], body: String) extends FunBody // NOTE `body` is actually the whole function! + + case class FunDef(id: Id, returnType: Type, ctx: Seq[ValDef], params: Seq[ValDef], var body: FunBody) extends Def { + // Ignore body in equality/hash code; actually, use only the identifier. This is to prevent infinite recursion... + override def equals(that: Any): Boolean = that match { + case fd: FunDef => fd.id == id + case _ => false + } + + override def hashCode: Int = id.hashCode + } + + case class ClassDef(id: Id, parent: Option[ClassDef], fields: Seq[ValDef], isAbstract: Boolean) extends Def { + require( + // Parent must be abstract if any + (parent forall { _.isAbstract }) && + // No fields on abstract classes (isAbstract --> fields.isEmpty) + (!isAbstract || fields.isEmpty) + ) + + // Find the "toppest" parent + val hierarchyTop: ClassDef = (parent map { _.hierarchyTop }) getOrElse this + + // To make the list of children correct by construction, subclasses + // get automatically registered to their parent + parent foreach { _ addChild this } + + // The list of children is kept directly here, in a mutable manner, + // so that we can easily refine all the classes from a class hierarchy + // at once, plus actually keep track of all of them. + private var _children = Set[ClassDef]() + + private def addChild(child: ClassDef): Unit = { + require(isAbstract) + + _children = _children + child + } + + private def getAllChildren: Set[ClassDef] = _children flatMap { c => c.getAllChildren + c } + + def getDirectChildren: Set[ClassDef] = _children + + def getFullHierarchy: ClassHierarchy = hierarchyTop.getAllChildren + hierarchyTop + + def getHierarchyLeaves: Set[ClassDef] = getFullHierarchy filter { !_.isAbstract } + + // Get the type of a given field + def getFieldType(fieldId: Id): Type = fields collectFirst { case ValDef(id, typ, _) if id == fieldId => typ } match { + case Some(typ) => typ + case None => ??? + } + } + + case class ValDef(id: Id, typ: Type, isVar: Boolean) extends Def { + def getType: Type = typ + def isArray: Boolean = typ.isArray + def isMutable: Boolean = isVar || typ.isMutable + def isReference: Boolean = typ.isReference + } + + /**************************************************************************************************** + * Array Allocation * + ****************************************************************************************************/ + // Represent an allocation point for arrays + // + // NOTE This ain't an Expr. + sealed abstract class ArrayAlloc extends Tree { + val typ: ArrayType + } + + // Allocate an array with a compile-time size + case class ArrayAllocStatic(typ: ArrayType, length: Int, values: Seq[Expr]) extends ArrayAlloc { + require( + // The type of the values should exactly match the type of the array elements + (values forall { _.getType == typ.base }) && + // The number of values should match the array size + (length == values.length) && + // And empty arrays are forbidden + (length > 0) + ) + } + + // Allocate a variable length array (VLA) + // + // NOTE Using "call by name" on `valueInit` means it will be fully evaluated at runtime as + // many times as the runtime value of `length`. + case class ArrayAllocVLA(typ: ArrayType, length: Expr, valueInit: Expr) extends ArrayAlloc { + require( + // The length must evaluate to an integer (and should be positif but this not tested) + (length.getType == PrimitiveType(Int32Type)) && + // The type of the array elements should match the type of the initialiation expression + (valueInit.getType == typ.base) + ) + } + + /**************************************************************************************************** + * Expression Tree * + ****************************************************************************************************/ + sealed abstract class Expr extends Tree { + // Get the type of the expressions + final def getType: Type = this match { + case Binding(vd) => vd.getType + case Block(exprs) => exprs.last.getType + case Decl(_) => NoType + case DeclInit(_, _) => NoType + case App(fd, _, _) => fd.returnType + case Construct(cd, _) => ClassType(cd) + case ArrayInit(alloc) => alloc.typ + case FieldAccess(objekt, fieldId) => objekt.getType.asInstanceOf[ClassType].clazz getFieldType fieldId + case ArrayAccess(array, _) => array.getType.asInstanceOf[ArrayType].base + case ArrayLength(_) => PrimitiveType(Int32Type) + case Assign(_, _) => NoType + case BinOp(op, lhs, rhs) => op match { + case _: ToIntegral => lhs.getType // same type as input + case _: ToLogical => PrimitiveType(BoolType) + case _ => ??? + } + case UnOp(op, expr) => op match { + case _: ToIntegral => expr.getType + case _: ToLogical => PrimitiveType(BoolType) + case _ => ??? + } + case If(_, _) => NoType + case IfElse(_, thenn, _) => thenn.getType // same as elze + case While(_, _) => NoType + case IsA(_, _) => PrimitiveType(BoolType) + case AsA(_, ct) => ct + case Lit(lit) => PrimitiveType(lit.getPrimitiveType) + case Ref(e) => ReferenceType(e.getType) + case Deref(e) => e.getType.asInstanceOf[ReferenceType].t + case Return(e) => e.getType + case Break => NoType + } + } + + // Access a variable + case class Binding(vd: ValDef) extends Expr + + // A non-empty sequence of expressions + // + // NOTE Nested Decl & DeclInit expressions have their ValDef available to following expressions in the block. + // + // NOTE Use factory helper `buildBlock` below to flatten blocks together. + case class Block(exprs: Seq[Expr]) extends Expr { + require(exprs.nonEmpty) + } + + // A variable declaration without initialisation + // + // NOTE DeclInit is not present as such in the input program; it is introduced by normalisation/flattening. + case class Decl(vd: ValDef) extends Expr + + // A value/variable declaration & initialisation + case class DeclInit(vd: ValDef, value: Expr) extends Expr + + // Invoke a function with context & regular arguments + case class App(fd: FunDef, extra: Seq[Expr], args: Seq[Expr]) extends Expr + + // Construct an object with the given field values + case class Construct(cd: ClassDef, args: Seq[Expr]) extends Expr + + // Create an array on the stack + case class ArrayInit(alloc: ArrayAlloc) extends Expr + + // Access a field of an object + case class FieldAccess(objekt: Expr, fieldId: Id) extends Expr + + // Access an array element + case class ArrayAccess(array: Expr, index: Expr) extends Expr + + // Probe the length of an array + case class ArrayLength(array: Expr) extends Expr + + // Assign a value to a variable/field/array element + case class Assign(lhs: Expr, rhs: Expr) extends Expr + + // Binary & Unary operators + case class BinOp(op: BinaryOperator, lhs: Expr, rhs: Expr) extends Expr + case class UnOp(op: UnaryOperator, expr: Expr) extends Expr + + // Control flow: If, If+Else & While + case class If(cond: Expr, thenn: Expr) extends Expr + case class IfElse(cond: Expr, thenn: Expr, elze: Expr) extends Expr + case class While(cond: Expr, body: Expr) extends Expr + + // Type probindg + casting + case class IsA(expr: Expr, ct: ClassType) extends Expr + case class AsA(expr: Expr, ct: ClassType) extends Expr + + // Literals + case class Lit(lit: Literal) extends Expr + + // Referencing/Dereferencing, i.e. take address of expression or deference pointer + case class Ref(e: Expr) extends Expr { + require(!e.getType.isInstanceOf[ReferenceType]) + } + case class Deref(e: Expr) extends Expr { + require(e.getType.isInstanceOf[ReferenceType]) + } + + case class Return(e: Expr) extends Expr + + case object Break extends Expr + + + /**************************************************************************************************** + * Expression Helpers * + ****************************************************************************************************/ + + // Flatten blocks together + def buildBlock(exprs: Seq[Expr]): Block = { + require(exprs.nonEmpty) + + val block = (Seq[Expr]() /: exprs) { + case (acc, e) => e match { + case Block(exprs) => acc ++ exprs + case expr => acc :+ expr + } + } + + Block(block) + } + + val False = Lit(BoolLit(false)) + val True = Lit(BoolLit(true)) + + + /**************************************************************************************************** + * Type Tree * + ****************************************************************************************************/ + sealed abstract class Type extends Tree { + def isArray: Boolean = this match { + case ArrayType(_) => true + case _ => false + } + + def containsArray: Boolean = this match { + case PrimitiveType(_) => false + + // We do *NOT* answer this question for the whole class hierarchy! + case ClassType(clazz) => clazz.fields exists { _.getType.containsArray } + + case ArrayType(_) => true + case ReferenceType(t) => t.containsArray + + // We assume typedefs don't contain arrays + case TypedefType(_, _, _) => false + + case DroppedType => false + + case NoType => false + } + + def isMutable: Boolean = this match { + case PrimitiveType(_) => false + + // We do *NOT* answer this question for the whole class hierarchy! + case ClassType(clazz) => clazz.fields exists { _.isMutable } + + case ArrayType(_) => true + case ReferenceType(_) => true + case TypedefType(_, _, _) => false // This is our assumption + case DroppedType => false + case NoType => false + } + + def isReference: Boolean = this match { + case ReferenceType(_) => true + case _ => false + } + } + + // Type representing Int32, Boolean, ... + case class PrimitiveType(primitive: PT) extends Type + + // Type representing an abstract or case class, as well as tuples + case class ClassType(clazz: ClassDef) extends Type + + // Represents the type of an array of `base` + case class ArrayType(base: Type) extends Type + + // A ReferenceType is just a marker for references, it acts like the underlying type + case class ReferenceType(t: Type) extends Type + + // For @cCode.typedef + case class TypedefType(original: Id, alias: Id, include: Option[String]) extends Type + + // For @cCode.drop + // TODO Drop them completely, and reject input program if one dropped type is actually used! + case object DroppedType extends Type + + // For when an expressions has no specific type + case object NoType extends Type + + + /**************************************************************************************************** + * Type Helpers * + ****************************************************************************************************/ + + def repId(typ: Type): Id = typ match { + case PrimitiveType(pt) => pt match { + case CharType => "char" + case Int32Type => "int32" + case BoolType => "bool" + case UnitType => "unit" + case StringType => "string" + } + case ClassType(clazz) => clazz.id + case ArrayType(base) => "array_" + repId(base) + case ReferenceType(t) => "ref_" + repId(t) + case TypedefType(original, _, _) => original + case DroppedType => ??? // Building an id on dropped type is illegal! + case NoType => ??? // Idem for NoType + } + +} + +object IRs { + final object CIR extends IR + final object RIR extends IR + final object NIR extends IR + final object LIR extends IR +} + diff --git a/src/main/scala/leon/genc/ir/IRPrinter.scala b/src/main/scala/leon/genc/ir/IRPrinter.scala new file mode 100644 index 000000000..d9bd506e0 --- /dev/null +++ b/src/main/scala/leon/genc/ir/IRPrinter.scala @@ -0,0 +1,121 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +/* + * Print an IR tree + */ +final class IRPrinter[S <: IR](val ir: S) { + import ir._ + + case class Context(indent: Int) { + val pad = " " * indent + val newLine = s"\n$pad" + + def +(i: Int) = copy(indent = indent + i) + } + + // Entry point for pretty printing + final def apply(prog: ProgDef): String = rec(prog)(Context(0)) + + final def apply(tree: Tree)(implicit ptx: Context): String = tree match { + case t: ProgDef => rec(t) + case t: FunDef => rec(t) + case t: ClassDef => rec(t) + case t: ValDef => rec(t) + case t: Expr => rec(t) + case t: Type => rec(t) + case _ => ??? + } + + private def rec(prog: ProgDef)(implicit ptx: Context): String = { + val deps = new DependencyFinder(ir) + deps(prog.asInstanceOf[deps.ir.ProgDef]) // Weird cast we have to live with... + + val funs = deps.getFunctions map { _.asInstanceOf[FunDef] } map rec + val classes = deps.getClasses map { _.asInstanceOf[ClassDef] } map rec + + (funs mkString ptx.newLine) + ptx.newLine + (classes mkString ptx.newLine) + } + + private def rec(fd: FunDef)(implicit ptx: Context): String = { + val ctx = fd.ctx map rec mkString ", " + val params = (fd.params map rec).mkString(start = ", ", sep = ", ", end = "") + val pre = fd.id + "(<" + ctx + ">" + params + "): " + rec(fd.returnType) + " = {" + ptx.newLine + " " + val body = rec(fd.body)(ptx + 1) + val post = ptx.newLine + "}" + + pre + body + post + } + + private def rec(fb: FunBody)(implicit ptx: Context): String = { + fb match { + case FunBodyAST(body) => rec(body) + case _ => "@cCode.function" + } + } + + private def rec(cd: ClassDef)(implicit ptx: Context): String = { + val pre = if (cd.isAbstract) "abstract " else "" + val fields = cd.fields map rec mkString ", " + val parent = if (cd.parent.isDefined) " extends " + cd.parent.get.id else "" + + pre + "class " + cd.id + "(" + fields + ")" + parent + } + + private def rec(vd: ValDef)(implicit ptx: Context): String = { + vd.id + ": " + rec(vd.typ) + } + + private def rec(alloc: ArrayAlloc)(implicit ptx: Context): String = { + (alloc: @unchecked) match { + case ArrayAllocStatic(arrayType, length, values) => "Array[" + rec(arrayType.base) + "](" + (values map rec mkString ", ") + ")" + case ArrayAllocVLA(arrayType, length, valueInit) => "Array[" + rec(arrayType.base) + "].fill(" + rec(length) + ")(" + rec(valueInit) + ")" + } + } + + private def rec(e: Expr)(implicit ptx: Context): String = (e: @unchecked) match { + case Binding(vd) => vd.id + case Block(exprs) => "{{ " + (exprs map rec mkString ptx.newLine) + " }}" + case Decl(vd) => (if (vd.isVar) "var" else "val") + " " + rec(vd) + " // no value" + case DeclInit(vd, value) => (if (vd.isVar) "var" else "val") + " " + rec(vd) + " = " + rec(value) + case App(fd, extra, args) => + fd.id + "(<" + (extra map rec mkString ", ") + ">" + (args map rec).mkString(start = ", ", sep = ", ", end = "") + ")" + case Construct(cd, args) => cd.id + "(" + (args map rec mkString ", ") + ")" + case ArrayInit(alloc) => rec(alloc) + case FieldAccess(objekt, fieldId) => rec(objekt) + "." + fieldId + case ArrayAccess(array, index) => rec(array) + "[" + rec(index) + "]" + case ArrayLength(array) => rec(array) + ".length" + case Assign(lhs, rhs) => rec(lhs) + " = " + rec(rhs) + case BinOp(op, lhs, rhs) => rec(lhs) + " " + op.symbol + " " + rec(rhs) + case UnOp(op, expr) => op.symbol + rec(expr) + case If(cond, thenn) => + "if (" + rec(cond) + ") {" + ptx.newLine + " " + rec(thenn)(ptx + 1) + ptx.newLine + "}" + case IfElse(cond, thenn, elze) => + "if (" + rec(cond) + ") {" + ptx.newLine + " " + rec(thenn)(ptx + 1) + ptx.newLine + "} " + + "else {" + ptx.newLine + " " + rec(elze)(ptx + 1) + ptx.newLine + "}" + case While(cond, body) => + "while (" + rec(cond) + ") {" + ptx.newLine + " " + rec(body)(ptx + 1) + ptx.newLine + "}" + case IsA(expr, ct) => "¿" + ct.clazz.id + "?" + rec(expr) + case AsA(expr, ct) => "(" + ct.clazz.id + ")" + rec(expr) + case Lit(lit) => lit.toString + case Ref(e) => "&" + rec(e) + case Deref(e) => "*" + rec(e) + case Return(e) => "return " + rec(e) + case Break => "break" + } + + private def rec(typ: Type)(implicit ptx: Context): String = (typ: @unchecked) match { + case PrimitiveType(pt) => pt.toString + case ClassType(clazz) => clazz.id + case ArrayType(base) => "Array[" + rec(base) + "]" + case ReferenceType(t) => "Ref[" + rec(t) + "]" + case TypedefType(original, alias, _) => "typedef " + original + " -> " + alias + case DroppedType => "DROPPED" + case NoType => "NO-TYPE" + } + +} + diff --git a/src/main/scala/leon/genc/ir/Literals.scala b/src/main/scala/leon/genc/ir/Literals.scala new file mode 100644 index 000000000..3cfa9c5bf --- /dev/null +++ b/src/main/scala/leon/genc/ir/Literals.scala @@ -0,0 +1,61 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import PrimitiveTypes._ + +/* + * Collection of literal genres for IR. + */ +private[genc] object Literals { + + sealed abstract class Literal { + def getPrimitiveType: PrimitiveType = this match { + case CharLit(_) => CharType + case IntLit(_) => Int32Type + case BoolLit(_) => BoolType + case UnitLit => UnitType + case StringLit(_) => StringType + } + + override def toString: String = this match { + case CharLit(v) => "'" + escape(v) + "'" + case IntLit(v) => s"$v" + case BoolLit(v) => s"$v" + case UnitLit => s"()" + case StringLit(v) => '"' + escape(v) + '"' + } + } + + case class CharLit(v: Char) extends Literal + + case class IntLit(v: Int) extends Literal + + case class BoolLit(v: Boolean) extends Literal + + case object UnitLit extends Literal + + case class StringLit(v: String) extends Literal + + /** Some little helper to properly print char/string literals **/ + private def escape(c: Char): String = c match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '\\' => "\\\\" + case '\'' => "\\'" + case '\"' => "\\\"" + case c => c.toString + } + + private def escape(s: String): String = { + import org.apache.commons.lang3.StringEscapeUtils + StringEscapeUtils.escapeJava(s) + } + +} + diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala new file mode 100644 index 000000000..336d53ace --- /dev/null +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -0,0 +1,288 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import PrimitiveTypes.{ PrimitiveType => PT, _ } // For desambiguation +import Literals._ +import Operators._ +import IRs._ + +/* + * Flatten out block expressions in argument-like position (e.g. function argument, while + * condition, ...) and ensure execution order match between Scala & C execution models by + * adding explicit execution points. + */ +final class Normaliser(val ctx: LeonContext) extends Transformer(RIR, NIR) with MiniReporter with NoEnv { + import from._ + + // Inject return in functions that need it + override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = super.recImpl(fd) match { + case fd @ to.FunDef(_, returnType, _, _, to.FunBodyAST(body)) if !isUnitType(returnType) => + val newBody = to.FunBodyAST(inject({ e => to.Return(e) })(body)) + + fd.body = newBody + + fd + + case fun => fun + } + + private def recAT(typ: ArrayType)(implicit env: Env) = rec(typ).asInstanceOf[to.ArrayType] + private def recCT(typ: ClassType)(implicit env: Env) = rec(typ).asInstanceOf[to.ClassType] + + override def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = e match { + case _: Binding | _: Lit | _: Block | _: Deref => super.recImpl(e) + + case DeclInit(vd0, ArrayInit(alloc0)) => + val vd = rec(vd0) + + val (preAlloc, alloc) = alloc0 match { + case ArrayAllocStatic(typ, length, values0) => + val (preValues, values) = flattens(values0) + val alloc = to.ArrayAllocStatic(recAT(typ), length, values) + + preValues -> alloc + + case ArrayAllocVLA(typ, length0, valueInit0) => + val (preLength, length) = flatten(length0) + val (preValueInit, valueInit) = flatten(valueInit0) + + if (preValueInit.nonEmpty) + fatalError(s"VLA elements cannot be initialised with a complex expression") + + val alloc = to.ArrayAllocVLA(recAT(typ), length, valueInit) + + preLength -> alloc + } + + val declinit = to.DeclInit(vd, to.ArrayInit(alloc)) + + combine(preAlloc :+ declinit) -> env + + case DeclInit(vd0, value0) => + val vd = rec(vd0) + val (pre, value) = flatten(value0) + + val declinit = to.DeclInit(vd, value) + + combine(pre :+ declinit) -> env + + case App(fd0, extra0, args0) => + val fd = rec(fd0) + val extra = extra0 map rec // context argument are trivial enough to not require special handling + val (preArgs, args) = flattens(args0) + val app = to.App(fd, extra, args) + + combine(preArgs :+ app) -> env + + case Construct(cd0, args0) => + val cd = rec(cd0) + val (preArgs, args) = flattens(args0) + val ctor = to.Construct(cd, args) + + combine(preArgs :+ ctor) -> env + + case ai @ ArrayInit(_) => super.recImpl(ai) // this will be handled later + + case FieldAccess(objekt0, fieldId) => + val (preObjekt, objekt) = flatten(objekt0) + val access = to.FieldAccess(objekt, fieldId) + + combine(preObjekt :+ access) -> env + + case ArrayAccess(array0, index0) => + val (preArray, array) = flatten(array0) + val (preIndex, index) = flatten(index0) + val access = to.ArrayAccess(array, index) + + combine(preArray ++ preIndex :+ access) -> env + + case ArrayLength(array0) => + val (preArray, array) = flatten(array0) + val length = to.ArrayLength(array) + + combine(preArray :+ length) -> env + + case Assign(lhs0, rhs0) => + val (preLhs, lhs) = flatten(lhs0) + val (preRhs, rhs) = flatten(rhs0) + val assign = to.Assign(lhs, rhs) + + combine(preLhs ++ preRhs :+ assign) -> env + + case BinOp(op, lhs0, rhs0) => + val (preLhs, lhs) = flatten(lhs0) + val (preRhs, rhs) = flatten(rhs0) + + def default = { + val binop = to.BinOp(op, lhs, rhs) + combine(preLhs ++ preRhs :+ binop) -> env + } + + def short(thenn: to.Expr, elze: to.Expr) = { + val expr = to.IfElse(lhs, thenn, elze) + combine(preLhs :+ expr) -> env + } + + // Handle short-circuiting + if (preRhs.isEmpty) default + else op match { + case And => short(combine(preRhs :+ rhs), to.False) + case Or => short(to.True, combine(preRhs :+ rhs)) + case _ => default + } + + case UnOp(op, expr0) => + val (pre, expr) = flatten(expr0) + val unop = to.UnOp(op, expr) + + combine(pre :+ unop) -> env + + case If(cond0, thenn0) => + val (preCond, cond) = flatten(cond0) + val thenn = rec(thenn0) + + val fi = to.If(cond, thenn) + + combine(preCond :+ fi) -> env + + case IfElse(cond0, thenn0, elze0) => + val (preCond, cond) = flatten(cond0) + val thenn = rec(thenn0) + val elze = rec(elze0) + + val fi = to.IfElse(cond, thenn, elze) + + combine(preCond :+ fi) -> env + + case While(cond0, body0) => + val (preCond, cond) = flatten(cond0) + val body = rec(body0) + + val loop = preCond match { + case Seq() => to.While(cond, body) + case preCond => + // Transform while ({ preCond; cond }) { body } into + // while (true) { preCond; if (cond) { body } else { break } } + to.While(to.True, to.buildBlock(preCond :+ to.IfElse(cond, body, to.buildBlock(to.Break :: Nil)))) + } + + loop -> env + + case IsA(expr0, ct0) => + val ct = recCT(ct0) + val (preExpr, expr) = flatten(expr0) + val isa = to.IsA(expr, ct) + + combine(preExpr :+ isa) -> env + + case AsA(expr0, ct0) => + val ct = recCT(ct0) + val (preExpr, expr) = flatten(expr0) + val asa = to.AsA(expr, ct) + + combine(preExpr :+ asa) -> env + + case Ref(e0) => + val (pre, e) = flatten(e0) + val ref = to.Ref(e) + + combine(pre :+ ref) -> env + + case _ => internalError(s"$e is not handled (${e.getClass}) by the normaliser") + } + + private def combine(es: Seq[to.Expr]): to.Expr = es match { + case Seq() => internalError("no argument") + case Seq(e) => e // don't introduce block if we can + case es => to.buildBlock(es) + } + + private def flatten(e: Expr)(implicit env: Env): (Seq[to.Expr], to.Expr) = rec(e) match { + case to.Block(init :+ last) => normalise(init, last) + case e => normalise(Seq.empty, e) + } + + // Extract all "init" together + private def flattens(es: Seq[Expr])(implicit env: Env): (Seq[to.Expr], Seq[to.Expr]) = { + val empty = (Seq[to.Expr](), Seq[to.Expr]()) // initS + lastS + (empty /: es) { case ((inits, lasts), e) => + val (init, last) = flatten(e) + (inits ++ init, lasts :+ last) + } + } + + private def normalise(pre: Seq[to.Expr], value: to.Expr): (Seq[to.Expr], to.Expr) = value match { + case fi0 @ to.IfElse(_, _, _) => + val norm = freshNormVal(fi0.getType, isVar = true) + val decl = to.Decl(norm) + val binding = to.Binding(norm) + val fi = inject({ e => to.Assign(binding, e) })(fi0) + + (pre :+ decl :+ fi, binding) + + case app @ to.App(fd, _, _) => + // Add explicit execution point by saving the result in a temporary variable + val norm = freshNormVal(fd.returnType, isVar = false) + val declinit = to.DeclInit(norm, app) + val binding = to.Binding(norm) + + (pre :+ declinit, binding) + + case ai @ to.ArrayInit(_) => + // Attach the ArrayInit to a DeclInit + // Go backwards to reuse code from the main recImpl function + debug(s"going backward on $ai") + val ai0 = backward(ai) + val norm = freshNormVal(ai.getType, isVar = false) + val norm0 = backward(norm) + val declinit0 = from.DeclInit(norm0, ai0) + val binding = to.Binding(norm) + + val (preDeclinit, declinit) = flatten(declinit0)(Ø) + + (preDeclinit :+ declinit, binding) + + case to.Assign(_, _) => internalError("unexpected assignement") + + case value => (pre, value) + } + + // Apply `action` on the AST leaf expressions. + private def inject(action: to.Expr => to.Expr)(e: to.Expr): to.Expr = { + val injecter = new Transformer(to, to) with NoEnv { injecter => + import injecter.from._ + + override def recImpl(e: Expr)(implicit env: Env): (Expr, Env) = e match { + case Decl(_) | DeclInit(_, _) | Assign(_, _) | While(_, _) => + internalError(s"Injecting into unexpected expression: $e") + + case Block(es) => buildBlock(es.init :+ rec(es.last)) -> Ø + + case If(cond, thenn) => If(cond, rec(thenn)) -> Ø + + case IfElse(cond, thenn, elze) => IfElse(cond, rec(thenn), rec(elze)) -> Ø + + case e => action(e) -> Ø // no more recursion here + } + } + + injecter(e) + } + + private def isUnitType(typ: to.Type): Boolean = typ match { + case to.PrimitiveType(UnitType) => true + case _ => false + } + + private def freshNormVal(typ: to.Type, isVar: Boolean) = to.ValDef(freshId("norm"), typ, isVar) + + private def freshId(id: String): to.Id = id + "_" + freshCounter.next(id) + + private val freshCounter = new utils.UniqueCounter[String]() + + private val backward = new Transformer(NIR, RIR) with NoEnv +} + diff --git a/src/main/scala/leon/genc/ir/Operators.scala b/src/main/scala/leon/genc/ir/Operators.scala new file mode 100644 index 000000000..01824c3ba --- /dev/null +++ b/src/main/scala/leon/genc/ir/Operators.scala @@ -0,0 +1,70 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import PrimitiveTypes._ + +/* + * Collection of operators for IR with their precedence from the Scala language perspective. + */ +private[genc] object Operators { + + // NOTE It is possible to have more than one "From", but not several "To". + // (It will compile but ungracefully do not what is expected...) + // + // NOTE It is also expected that ToIntegral is combined with FromIntegral. + // + // NOTE The subset of operators supported here has luckily the same precedence + // rules in Scala/Java and C. We base the numbering here on the C one: + // http://en.cppreference.com/w/c/language/operator_precedence#Literals + sealed trait Operator { this: From with To => + val symbol: String + val precedence: Int + + override def toString = symbol + } + + sealed trait From + trait FromIntegral extends From + trait FromLogical extends From + + sealed trait To + trait ToIntegral extends To + trait ToLogical extends To + + trait Integral extends FromIntegral with ToIntegral + trait Logical extends FromLogical with ToLogical + trait Comparative extends FromIntegral with ToLogical + + abstract class UnaryOperator(val symbol: String, val precedence: Int) extends Operator { this: From with To => } + abstract class BinaryOperator(val symbol: String, val precedence: Int) extends Operator { this: From with To => } + + case object Plus extends BinaryOperator("+", 4) with Integral + case object UMinus extends UnaryOperator("-", 2) with Integral + case object Minus extends BinaryOperator("-", 4) with Integral + case object Times extends BinaryOperator("*", 3) with Integral + case object Div extends BinaryOperator("/", 3) with Integral + case object Modulo extends BinaryOperator("%", 3) with Integral + + case object LessThan extends BinaryOperator("<", 6) with Comparative + case object LessEquals extends BinaryOperator("<=", 6) with Comparative + case object GreaterEquals extends BinaryOperator(">=", 6) with Comparative + case object GreaterThan extends BinaryOperator(">", 6) with Comparative + case object Equals extends BinaryOperator("==", 7) with FromIntegral with FromLogical with ToLogical + case object NotEquals extends BinaryOperator("!=", 7) with FromIntegral with FromLogical with ToLogical + + case object Not extends UnaryOperator("!", 2) with Logical + case object And extends BinaryOperator("&&", 11) with Logical + case object Or extends BinaryOperator("||", 12) with Logical + + case object BNot extends UnaryOperator("~", 2) with Integral + case object BAnd extends BinaryOperator("&", 8) with Integral + case object BXor extends BinaryOperator("^", 9) with Integral + case object BOr extends BinaryOperator("|", 10) with Integral + case object BLeftShift extends BinaryOperator("<<", 5) with Integral + case object BRightShift extends BinaryOperator(">>", 5) with Integral + +} + diff --git a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala new file mode 100644 index 000000000..14a78070a --- /dev/null +++ b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala @@ -0,0 +1,21 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +/* + * Collection of the primitive types for IR. + */ +private[genc] object PrimitiveTypes { + + sealed abstract class PrimitiveType + + case object CharType extends PrimitiveType + case object Int32Type extends PrimitiveType + case object BoolType extends PrimitiveType + case object UnitType extends PrimitiveType + case object StringType extends PrimitiveType + +} + diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala new file mode 100644 index 000000000..47856751a --- /dev/null +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -0,0 +1,140 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import PrimitiveTypes.{ PrimitiveType => PT, _ } // For desambiguation +import Literals._ +import Operators._ +import IRs._ + +/* + * The main idea is to add ReferenceType on functions' parameters when either: + * - the parameter is not an array (C array are always passed by reference using a different mechanism) + * - the parameter is part of the context of the function + * - the parameter is of mutable type + * + * This also means that when using a binding in an expression we have to make sure that we + * take a reference or dereference it before using it. + * + * Array are mutables, but once converted into C they are wrapped into an immutable struct; + * we therefore do not take array by reference because this would only add an indirection + */ +final class Referentiator(val ctx: LeonContext) extends Transformer(CIR, RIR) with MiniReporter { + import from._ + + type Env = Map[CIR.ValDef, RIR.ValDef] + val Ø = Map[CIR.ValDef, RIR.ValDef]() + + override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { + val id = fd.id + + val returnType = rec(fd.returnType) // nothing special thanks to the no-aliasing rule + + val ctx = fd.ctx map { c0 => + // except for arrays, add ReferenceType + val c = rec(c0) + if (c.isArray) c + else toReference(c) + } + + val params = fd.params map { p0 => + // except for arrays, add ReferenceType to mutable types + val p = rec(p0) + if (p.isArray) p + else if (p.isMutable) toReference(p) + else p + } + + // Build the new environment: from CIR to RIR + val newEnv = env ++ ((fd.ctx ++ fd.params) zip (ctx ++ params)) + + // Handle recursive functions + val newer = to.FunDef(id, returnType, ctx, params, null) + registerFunction(fd, newer) + + newer.body = rec(fd.body)(newEnv) + + newer + } + + // When converting a binding we add Deref if the variable is known to be + // a reference. When converting, say, a function call we add Ref if the + // expected parameter is of ReferenceType, or Deref in the opposite + // situation. + // + // These operations can introduce pattern such as Deref(Ref(_)) + // or Ref(Deref(_)). This is of course not what we want so we fix it + // right away using the ref & deref factory functions. + final override def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = e match { + case Binding(vd0) => + // Check the environment for id; if it's a ref we have to reference it. + val vd = env(vd0) + val b = RIR.Binding(vd) + if (vd.isReference) deref(b) -> env + else b -> env + + case Decl(_) => internalError("Decl is expected only after normalisation/flattening") + + case DeclInit(vd0, value0) => + val vd = rec(vd0) + val value = rec(value0) + val newEnv = env + (vd0 -> vd) + to.DeclInit(vd, value) -> newEnv + + case Ref(_) | Deref(_) => internalError("Ref & Deref expression should not yet be present") + + case App(fd0, extra0, args0) => + // Add Ref/Deref to extra/args when appropriate + val fd = rec(fd0) + val extra = refMatch(fd.ctx)(extra0 map rec) + val args = refMatch(fd.params)(args0 map rec) + + to.App(fd, extra, args) -> env + + case Construct(cd0, args0) => + val cd = rec(cd0) + val args = refMatch(cd.fields)(args0 map rec) + + to.Construct(cd, args) -> env + + case e => super.recImpl(e) + } + + // Adapt the expressions to match w.r.t. references the given parameter types, for argument-like expressions. + private def refMatch(params: Seq[RIR.ValDef])(args: Seq[RIR.Expr]): Seq[RIR.Expr] = { + (params zip args) map { case (param, arg) => + val pr = param.isReference + val ar = arg.getType.isReference + + (pr, ar) match { + case (false, false) | (true, true) => arg + case (false, true) => deref(arg) + case (true, false) => ref(arg, shortLived = true) + } + } + } + + // Build Ref & Deref expression without patterns such as Ref(Deref(_)) + private def ref(e: RIR.Expr, shortLived: Boolean = false): RIR.Expr = e match { + case b @ RIR.Binding(_) => RIR.Ref(b) + case RIR.Deref(e) => e + + // NOTE Reference can be build on Constructor, but we have to make sure we + // don't take the reference of a temporary result for a too long period. + case ctor @ RIR.Construct(_, _) if shortLived => RIR.Ref(ctor) + + case _ => internalError(s"Making reference on an unsupported expression: $e") + } + + private def deref(e: RIR.Expr): RIR.Expr = e match { + case b @ RIR.Binding(vd) if vd.isReference => RIR.Deref(b) + case RIR.Ref(e) => e + case _ => internalError(s"Dereferencing an unsupported expression: $e") + } + + private def toReference(vd: RIR.ValDef) = vd.copy(typ = RIR.ReferenceType(vd.typ)) + +} + diff --git a/src/main/scala/leon/genc/ir/Transformer.scala b/src/main/scala/leon/genc/ir/Transformer.scala new file mode 100644 index 000000000..62bd02947 --- /dev/null +++ b/src/main/scala/leon/genc/ir/Transformer.scala @@ -0,0 +1,153 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import PrimitiveTypes.{ PrimitiveType => PT, _ } // For desambiguation +import Literals._ +import Operators._ + +import collection.mutable.{ Map => MutableMap } + +trait NoEnv { + type Env = Unit + final val Ø = () +} + +/* + * Transform an IR into another version of the IR. + * + * NOTE Subclasses can selectively override the rec/recImpl methods that aren't final on a need to basis. + * + * NOTE Subclasses have to define an `Env` type and an empty enviornment Ø. They can be Unit and (), + * respectively. In that case, inherit from NoEnv. + * + * NOTE No caching is done, hence subclasses should be aware that a definition/expression/type can + * be transformed several times with the same env. + * EXCEPT for FunDef. In order to support recursive functions, function definitions are cached. + * When building partial function definitions (i.e. with body = null), subclasses are required + * to call `registerFunction` before proceeding with the conversion of their bodies. + */ +abstract class Transformer[From <: IR, To <: IR](final val from: From, final val to: To) { + import from._ + + type Env + val Ø: Env // empty env + + + // Entry point of the transformation + final def apply(prog: ProgDef): to.ProgDef = rec(prog)(Ø) + final def apply(vd: ValDef): to.ValDef = rec(vd)(Ø) + final def apply(e: Expr): to.Expr = rec(e)(Ø) + final def apply(typ: Type): to.Type = rec(typ)(Ø) + + + // See note above about caching & partial function definition + private val funCache = MutableMap[FunDef, to.FunDef]() + + protected final def registerFunction(older: FunDef, newer: to.FunDef) { + funCache.update(older, newer) + } + + protected def rec(prog: ProgDef)(implicit env: Env): to.ProgDef = + to.ProgDef(rec(prog.entryPoint)) + + protected final def rec(fd: FunDef)(implicit env: Env): to.FunDef = funCache.getOrElse(fd, recImpl(fd)) + + protected def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { + val newer = to.FunDef(fd.id, rec(fd.returnType), fd.ctx map rec, fd.params map rec, null) + registerFunction(fd, newer) + newer.body = rec(fd.body) + newer + } + + protected def rec(fb: FunBody)(implicit env: Env): to.FunBody = (fb: @unchecked) match { + case FunBodyAST(body) => to.FunBodyAST(rec(body)) + case FunBodyManual(includes, body) => to.FunBodyManual(includes, body) + } + + // NOTE Due to the mutability nature of ClassDef and its children registration process, + // we need to traverse class hierarchies in a top down fashion. See recImpl. + protected final def rec(cd: ClassDef)(implicit env: Env): to.ClassDef = { + type ClassTranslation = Map[from.ClassDef, to.ClassDef] + + def topDown(transformedParent: Option[to.ClassDef], current: from.ClassDef, acc: ClassTranslation): ClassTranslation = { + val transformed = recImpl(current, transformedParent) + + val acc2 = acc + (current -> transformed) + val subs = current.getDirectChildren + + (acc2 /: subs) { case (acc3, sub) => topDown(Some(transformed), sub, acc3) } + } + + val top = cd.hierarchyTop + val translation = topDown(None, top, Map.empty) + val transformed = translation(cd) + transformed + } + + protected def recImpl(cd: ClassDef, parent: Option[to.ClassDef])(implicit env: Env): to.ClassDef = + to.ClassDef(cd.id, parent, cd.fields map rec, cd.isAbstract) + + protected def rec(vd: ValDef)(implicit env: Env): to.ValDef = to.ValDef(vd.id, rec(vd.typ), vd.isVar) + + protected def rec(alloc: ArrayAlloc)(implicit env: Env): to.ArrayAlloc = (alloc: @unchecked) match { + case ArrayAllocStatic(ArrayType(base), length, values) => + to.ArrayAllocStatic(to.ArrayType(rec(base)), length, values map rec) + + case ArrayAllocVLA(ArrayType(base), length, valueInit) => + to.ArrayAllocVLA(to.ArrayType(rec(base)), rec(length), rec(valueInit)) + } + + protected final def rec(e: Expr)(implicit env: Env): to.Expr = recImpl(e)._1 + + // We need to propagate the enviornement accross the whole blocks, not simply by recusring + protected def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = (e: @unchecked) match { + case Binding(vd) => to.Binding(rec(vd)) -> env + + // Consider blocks as a sequence of let statements + case Block(exprs0) => + var newEnv = env + val exprs = for { e0 <- exprs0 } yield { + val (e, nextEnv) = recImpl(e0)(newEnv) + newEnv = nextEnv + e + } + to.buildBlock(exprs) -> newEnv + + case Decl(vd) => to.Decl(rec(vd)) -> env + case DeclInit(vd, value) => to.DeclInit(rec(vd), rec(value)) -> env + case App(fd, extra, args) => to.App(rec(fd), extra map rec, args map rec) -> env + case Construct(cd, args) => to.Construct(rec(cd), args map rec) -> env + case ArrayInit(alloc) => to.ArrayInit(rec(alloc)) -> env + case FieldAccess(objekt, fieldId) => to.FieldAccess(rec(objekt), fieldId) -> env + case ArrayAccess(array, index) => to.ArrayAccess(rec(array), rec(index)) -> env + case ArrayLength(array) => to.ArrayLength(rec(array)) -> env + case Assign(lhs, rhs) => to.Assign(rec(lhs), rec(rhs)) -> env + case BinOp(op, lhs, rhs) => to.BinOp(op, rec(lhs), rec(rhs)) -> env + case UnOp(op, expr) => to.UnOp(op, rec(expr)) -> env + case If(cond, thenn) => to.If(rec(cond), rec(thenn)) -> env + case IfElse(cond, thenn, elze) => to.IfElse(rec(cond), rec(thenn), rec(elze)) -> env + case While(cond, body) => to.While(rec(cond), rec(body)) -> env + case IsA(expr, ct) => to.IsA(rec(expr), to.ClassType(rec(ct.clazz))) -> env + case AsA(expr, ct) => to.AsA(rec(expr), to.ClassType(rec(ct.clazz))) -> env + case Lit(lit) => to.Lit(lit) -> env + case Ref(e) => to.Ref(rec(e)) -> env + case Deref(e) => to.Deref(rec(e)) -> env + case Return(e) => to.Return(rec(e)) -> env + case Break => to.Break -> env + } + + protected def rec(typ: Type)(implicit env: Env): to.Type = (typ: @unchecked) match { + case PrimitiveType(pt) => to.PrimitiveType(pt) + case ClassType(clazz) => to.ClassType(rec(clazz)) + case ArrayType(base) => to.ArrayType(rec(base)) + case ReferenceType(t) => to.ReferenceType(rec(t)) + case TypedefType(original, alias, include) => to.TypedefType(original, alias, include) + case DroppedType => to.DroppedType + case NoType => to.NoType + } + +} + diff --git a/src/main/scala/leon/genc/ir/Visitor.scala b/src/main/scala/leon/genc/ir/Visitor.scala new file mode 100644 index 000000000..f55b5f68e --- /dev/null +++ b/src/main/scala/leon/genc/ir/Visitor.scala @@ -0,0 +1,132 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package ir + +import collection.mutable.{ Set => MutableSet } + +/* + * Visite an IR in a post-order manner. + * + * NOTE Subclasses can selectively override the visite methods that aren't final on a need to basis. + * + * NOTE No caching is done, hence subclasses should be aware that a definition/expression/type can + * be visited several times with the same env. + * EXCEPT for functions. See Transformer for reason. + */ +abstract class Visitor[S <: IR](final val ir: S) { + import ir._ + + // Entry point for the visit + final def apply(prog: ProgDef): Unit = rec(prog) + + protected def visit(prog: ProgDef): Unit = () + protected def visit(fd: FunDef): Unit = () + protected def visit(cd: ClassDef): Unit = () + protected def visit(vd: ValDef): Unit = () + protected def visit(fb: FunBody): Unit = () + protected def visit(alloc: ArrayAlloc): Unit = () + protected def visit(e: Expr): Unit = () + protected def visit(typ: Type): Unit = () + + + private val funCache = MutableSet[FunDef]() + + + private def rec(prog: ProgDef): Unit = { + rec(prog.entryPoint) + visit(prog) + } + + private def rec(fd: FunDef): Unit = if (!funCache(fd)) { + funCache += fd // Traverse it once + + rec(fd.returnType) + fd.ctx foreach rec + fd.params foreach rec + rec(fd.body) + visit(fd) + } + + private def rec(fb: FunBody): Unit = { + fb match { + case FunBodyAST(body) => rec(body) + case _ => () + } + visit(fb) + } + + private def rec(cd: ClassDef): Unit = { + def impl(cd: ClassDef): Unit = { + cd.fields foreach rec + visit(cd) + } + + cd.getFullHierarchy foreach impl + } + + private def rec(vd: ValDef): Unit = { + rec(vd.typ) + visit(vd) + } + + private def rec(alloc: ArrayAlloc): Unit = { + (alloc: @unchecked) match { + case ArrayAllocStatic(arrayType, length, values) => + rec(arrayType) + values foreach rec + + case ArrayAllocVLA(arrayType, length, valueInit) => + rec(arrayType) + rec(length) + rec(valueInit) + } + + visit(alloc) + } + + private def rec(e: Expr): Unit = { + (e: @unchecked) match { + case Binding(vd) => rec(vd) + case Block(exprs) => exprs foreach rec + case Decl(vd) => rec(vd) + case DeclInit(vd, value) => rec(vd); rec(value) + case App(fd, extra, args) => rec(fd); extra foreach rec; args foreach rec + case Construct(cd, args) => rec(cd); args foreach rec + case ArrayInit(alloc) => rec(alloc) + case FieldAccess(objekt, fieldId) => rec(objekt) + case ArrayAccess(array, index) => rec(array); rec(index) + case ArrayLength(array) => rec(array) + case Assign(lhs, rhs) => rec(lhs); rec(rhs) + case BinOp(op, lhs, rhs) => rec(lhs); rec(rhs) + case UnOp(op, expr) => rec(expr) + case If(cond, thenn) => rec(cond); rec(thenn) + case IfElse(cond, thenn, elze) => rec(cond); rec(thenn); rec(elze) + case While(cond, body) => rec(cond); rec(body) + case IsA(expr, ct) => rec(expr); rec(ct.clazz) + case AsA(expr, ct) => rec(expr); rec(ct.clazz) + case Lit(lit) => () + case Ref(e) => rec(e) + case Deref(e) => rec(e) + case Return(e) => rec(e) + case Break => () + } + visit(e) + } + + private def rec(typ: Type): Unit = { + (typ: @unchecked) match { + case PrimitiveType(pt) => () + case ClassType(clazz) => rec(clazz) + case ArrayType(base) => rec(base) + case ReferenceType(t) => rec(t) + case TypedefType(original, alias, include) => () + case DroppedType => () + case NoType => () + } + visit(typ) + } + +} + diff --git a/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala b/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala new file mode 100644 index 000000000..b028f9ee3 --- /dev/null +++ b/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala @@ -0,0 +1,160 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import purescala.Common.{ Identifier } +import purescala.Definitions.{ ClassDef, Definition, FunDef, ModuleDef, Program } +import purescala.DefOps.{ pathFromRoot } +import purescala.{ TreeTraverser } + +import ExtraOps._ + +import collection.mutable.{ Set => MutableSet } + +/* + * Compute the dependencies of the main function and its mandatory "_main" sibling. + * + * The list of dependencies includes "_main" but not "main" as the later is + * annoted with @extern (cf. ExtractEntryPointPhase). + * + * Moreover, the list of dependencies only contains top level functions. For nested + * functions, we need to compute their "context" (i.e. capture free variable) to + * hoist them. This is done in a later phase. However, if a nested function uses + * some type T, then T (and all its class hierarchy if T is a class) will be included + * in the dependency set. + * + * This phase also make sure @cCode.drop function are not used. The same is *NOT* + * done for dropped types as they might still be present in function signature. They + * should be removed in a later (transformation) phase. Additionally, this phase + * ensures that the annotation set on class and function is sane. + * + * NOTE We cannot rely on purescala.DependencyFinder as it will traverse functions + * annotated with @cCode.function and we don't want that. The same applies for + * classes annotated with @cCode.typdef. We therefore implement a simpler version + * of it here based on a TreeTraverser. + */ +private[genc] object ComputeDependenciesPhase extends TimedLeonPhase[(Program, Definition), Dependencies] { + val name = "Dependency computer" + val description = "Compute the dependencies of a given definition" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("compute dependencies") + + def apply(ctx: LeonContext, input: (Program, Definition)): Dependencies = { + val (prog, entryPoint) = input + + val reporter = MiniReporter(ctx) + import reporter._ + + def isNestedFun(d: Definition): Boolean = d match { + case fd: FunDef => + val parentDef = pathFromRoot(fd)(prog).reverse(1) + + parentDef match { + case _: ModuleDef => false + case _: FunDef => true + case d => internalError(s"Unexpected definition ${d.id} of ${d.getClass} as parent of ${fd.id}") + } + + case _ => false + } + + def validateClassAnnotations(cd: ClassDef): Unit = { + val pos = cd.getPos + + if (cd.isManuallyTyped && cd.isDropped) + fatalError(s"${cd.id} cannot be both dropped and manually defined", pos) + + if (cd.isGeneric && cd.isManuallyTyped) + fatalError(s"${cd.id} cannot be both a generic type and manually defined", pos) + + if (cd.isManuallyTyped && cd.hasParent) + fatalError(s"${cd.id} cannot be manually defined because it is part of a class hierarchy", pos) + + if (cd.isRecursive) + fatalError(s"${cd.id} and other recursive types are not supported") + if (!cd.isManuallyTyped) { + if (cd.isRecursive) fatalError("Recursive types are not supported", pos) + if (cd.isCaseObject) fatalError("Case Objects are not supported", pos) + if (cd.methods.length > 0) internalError("Methods") // They should have been lifted before + } + } + + def validateFunAnnotations(fd: FunDef): Unit = { + val pos = fd.getPos + + // Those next three tests could be carried out on all functions, not just dependencies + if (fd.isExtern && !fd.isManuallyDefined && !fd.isDropped) + fatalError("Extern functions need to be either manually defined or dropped", pos) + + if (fd.isManuallyDefined && fd.isDropped) + fatalError("Functions cannot be dropped and manually implemented at the same time", pos) + + if (fd.isGeneric && fd.isManuallyDefined) + fatalError(s"Functions cannot be both a generic function and manually defined", pos) + + // This last test is specific to dependencies. + if (fd.isDropped) + fatalError(s"Dropped functions shall not be used", fd.getPos) + } + + val finder = new ComputeDependenciesImpl(ctx) + val allDeps = finder(entryPoint) + + // Ensure all annotations are sane on all dependencies, including nested functions. + allDeps foreach { + case fd: FunDef => validateFunAnnotations(fd) + case cd: ClassDef => validateClassAnnotations(cd) + case _ => () + } + + // Keep only the top level functions + val deps = allDeps filterNot isNestedFun + + deps + } +} + +// ComputeDependenciesImpl is agnostic to what should go or not in the dependency set; +// for example, nested functions will get registered. However, it will not traverse the body +// of function definition annotated with @cCode.function nor process fields of a @cCode.typedef +// class definition. +private final class ComputeDependenciesImpl(val ctx: LeonContext) extends MiniReporter with TreeTraverser { + private val dependencies = MutableSet[Definition]() + + // Compute the dependencies of `entry`, which includes itself. + def apply(entry: Definition): Dependencies = { + entry match { + case e: FunDef => traverse(e) + case _ => internalError("unexpected type of entry point: ${entry.getClass}") + } + + dependencies.toSet + } + + override def traverse(id: Identifier): Unit = traverse(id.getType) + + override def traverse(cd: ClassDef): Unit = if (!dependencies(cd)) { + dependencies += cd + + if (!cd.isManuallyTyped) { + // Visite the whole class hierarchy with their fields, recursiverly + cd.root.knownDescendants foreach traverse + cd.fieldsIds foreach traverse + } + } + + override def traverse(fd: FunDef): Unit = if (!dependencies(fd)) { + dependencies += fd + + if (!fd.isManuallyDefined) { + // Visite return type, body & arguments + traverse(fd.returnType) + traverse(fd.fullBody) + fd.params foreach { vd => traverse(vd.id) } + } + } + +} + diff --git a/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala new file mode 100644 index 000000000..b38839dd3 --- /dev/null +++ b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala @@ -0,0 +1,84 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import purescala.Common.{ Identifier } +import purescala.Definitions.{ FunDef, ValDef } +import purescala.Expressions._ +import purescala.Extractors.{ Operator } +import xlang.Expressions._ + +import scala.collection.mutable.{ Map => MutableMap } + +/* + * Compute, for each function definition, its context; i.e. roughly the set of free variable of its body. + * + * NOTE we are overapproximating the set of free variable by capturing any value defined "upstream" + * in the AST. + * + * NOTE in C99 there's the concept of strict aliasing (cf. §6.5/7), but since we don't do any weird + * cast operation in our translation, the overapproximation mentioned above is not an issue. + */ +private[genc] object ComputeFunCtxPhase extends TimedLeonPhase[Dependencies, FunCtxDB] { + val name = "Function context computer" + val description = "Compute the context of each given function definition" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("build function contexts database") + + def apply(ctx: LeonContext, deps: Dependencies): FunCtxDB = { + val reporter = MiniReporter(ctx) + import reporter._ + + type Ctx = Seq[VarInfo] + + val db = MutableMap[FunDef, Seq[VarInfo]]() + + def toVarInfo(vd: ValDef) = VarInfo(vd.id, vd.getType, vd.isVar) + + def processFunction(fd: FunDef, ctx: Ctx): Unit = { + debug(s"Registering ${fd.id.name} with ${ctx map { _.id } mkString ", "}.") + db += fd -> ctx + + // Recurse on body with an extended context + val ctx2 = ctx ++ (fd.params map toVarInfo) + rec(fd.fullBody, ctx2) + } + + def rec(expr: Expr, ctx: Ctx): Unit = expr match { + // Handle the interesting cases first, or they will fall into `case Operator(args, _)`. + case Let(binder, value, rest) => + rec(value, ctx) // binder not yet available here! + val ctx2 = ctx :+ VarInfo(binder, binder.getType, isVar = false) + rec(rest, ctx2) + + case LetVar(binder, value, rest) => + rec(value, ctx) // binder not yet available here! + val ctx2 = ctx :+ VarInfo(binder, binder.getType, isVar = true) + rec(rest, ctx2) + + case LetDef(fds, rest) => + // Register the nested functions, and recurse + fds foreach { fd => processFunction(fd, ctx) } + rec(rest, ctx) + + // Because technically a function could be defined a block which is itself an argument, + // we recurse on arguments as well! + // This also includes Terminal-like expression and therefore stop recursion when needed. + case Operator(args, _) => args foreach { arg => rec(arg, ctx) } + + case _ => internalError(s"NOT YET IMPLEMENTED: ctx computation for ${expr.getClass}") + } + + // Process every top level function to register function contextes for their inner functions; + // Register those top level functions as well + val topLevelFuns: Set[FunDef] = deps collect { case fd: FunDef => fd } + val Ø = Seq[VarInfo]() + topLevelFuns foreach { fd => processFunction(fd, Ø) } + + db.toMap // Make it immutable + } + +} + diff --git a/src/main/scala/leon/genc/phases/ExtraOps.scala b/src/main/scala/leon/genc/phases/ExtraOps.scala new file mode 100644 index 000000000..143af4f3c --- /dev/null +++ b/src/main/scala/leon/genc/phases/ExtraOps.scala @@ -0,0 +1,105 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import purescala.Definitions._ +import purescala.Types._ + +import collection.mutable.{ Set => MutableSet } + +private[genc] object ExtraOps { + + // Extra tools on FunDef, especially for annotations + implicit class FunDefOps(val fd: FunDef) { + def isMain = fd.id.name == "main" + + def isExtern = hasAnnotation("extern") + def isDropped = hasAnnotation("cCode.drop") + def isManuallyDefined = hasAnnotation(manualDefAnnotation) + + def getManualDefinition = { + assert(isManuallyDefined) + + val Seq(Some(code0), includesOpt0) = fd.extAnnotations(manualDefAnnotation) + val code = code0.asInstanceOf[String] + val includes0 = includesOpt0 map { _.asInstanceOf[String] } getOrElse "" + + val includes = + if (includes0.isEmpty) Nil + else { includes0 split ':' }.toSeq + + ManualDef(code.stripMargin, includes) + } + + case class ManualDef(code: String, includes: Seq[String]) + + def isGeneric = fd.tparams.length > 0 + + private def hasAnnotation(annot: String) = fd.annotations contains annot + private val manualDefAnnotation = "cCode.function" + } + + // Extra tools on ClassDef, especially for annotations, inheritance & generics + implicit class ClassDefOps(val cd: ClassDef) { + def isManuallyTyped = hasAnnotation(manualTypeAnnotation) + def isDropped = hasAnnotation(droppedAnnotation) + + def getManualType = { + assert(isManuallyTyped) + + val Seq(Some(alias0), includesOpt0) = cd.extAnnotations(manualTypeAnnotation) + val alias = alias0.asInstanceOf[String] + val include = includesOpt0 map { _.asInstanceOf[String] } flatMap { i => + if (i.isEmpty) None else Some(i) + } + + ManualType(alias, include) + } + + case class ManualType(alias: String, include: Option[String]) + + def isCandidateForInheritance = cd.isAbstract || cd.hasParent + + def isGeneric = cd.tparams.length > 0 + + def isRecursive: Boolean = { + val defs = (cd.parent map { _.classDef }).toSet + cd + + val seens = MutableSet[ClassType]() + + def rec(typ: TypeTree): Boolean = typ match { + case t: ClassType if seens(t) => false + + case t: ClassType => + defs(t.classDef) || { + seens += t + + (t.fields map { _.getType } exists rec) || + (t.parent exists rec) || + (t.knownCCDescendants exists rec) + } + + case NAryType(ts, _) => ts exists rec + + case _ => false + } + + // Find out if the parent of cd or cd itself are involved in a type of a field + cd.fields map { _.getType } exists rec + } + + // Check whether the class has some fields or not + def isEmpty = cd.fields.isEmpty + + private def hasAnnotation(annot: String) = cd.annotations contains annot + private val manualTypeAnnotation = "cCode.typedef" + private val droppedAnnotation = "cCode.drop" + } + + // Extra tools on ClassType, expecially for inheritance + implicit class ClassTypeOps(val ct: ClassType) { + def getTopParent: ClassType = ct.parent map { _.getTopParent } getOrElse { ct } + } +} + diff --git a/src/main/scala/leon/genc/phases/ExtractEntryPointPhase.scala b/src/main/scala/leon/genc/phases/ExtractEntryPointPhase.scala new file mode 100644 index 000000000..304df34ef --- /dev/null +++ b/src/main/scala/leon/genc/phases/ExtractEntryPointPhase.scala @@ -0,0 +1,81 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import purescala.Definitions.{ Definition, FunDef, Program } +import purescala.Types.{ Int32Type, UnitType } + +/* + * Find the main & _main functions and perform a few sanity checks. + * + * This phase checks that: + * - there is only one main unit; + * - the main function is uniquely defined as a non-generic external function; + * - the _main function is uniquely defined as a non-generic, parameterless function; + * - _main return type is either Unit or Int. + */ +private[genc] object ExtractEntryPointPhase extends TimedLeonPhase[Program, Definition] { + val name = "Compute main function dependencies" + val description = "Check validity of main & _main functions, and identify their dependencies" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("extract main") + + def apply(ctx: LeonContext, prog: Program) = getEntryPoint(prog, MiniReporter(ctx)) + + // Find _main() and check the assumptions listed above + private def getEntryPoint(prog: Program, reporter: MiniReporter): FunDef = { + import reporter._ + import ExtraOps._ + + val mainUnit = { + val mainUnits = prog.units filter { _.isMainUnit } + + // Make sure exactly one main unit is defined -- this is just a sanity check + if (mainUnits.size == 0) fatalError("No main unit in the program") + if (mainUnits.size >= 2) fatalError("Multiple main units in the program") + + mainUnits.head + } + + def getFunDef(name: String): FunDef = { + val results = mainUnit.definedFunctions filter { _.id.name == name } + + // Make sure there is no ambiguity about the name and that the function is defined + if (results.size == 0) fatalError(s"No $name was defined in unit ${mainUnit.id.uniqueName}") + if (results.size == 2) fatalError(s"Multiple $name were defined in unit ${mainUnit.id}") + + results.head + } + + val mainFd = getFunDef("main") + val _mainFd = getFunDef("_main") + + // Checks that "main" is properly defined + if (mainFd.isGeneric) + fatalError("The main function should not be generic", mainFd.getPos) + + if (!mainFd.isExtern) + fatalError("It is expected for `main(args)` to be extern", mainFd.getPos) + + // Idem for "_main" + if (_mainFd.params.size > 0) + fatalError("_main() should have no parameter", _mainFd.getPos) + + if (_mainFd.isGeneric) + fatalError("_main() should not be generic", _mainFd.getPos) + + if (_mainFd.isExtern) + fatalError("_main() should not be extern", _mainFd.getPos) + + _mainFd.returnType match { + case UnitType | Int32Type => // valid + case _ => fatalError("_main() should either return an integer or nothing", _mainFd.getPos) + } + + _mainFd + } +} + + diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala new file mode 100644 index 000000000..54031f9a3 --- /dev/null +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -0,0 +1,420 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import ir.IRs.{ LIR } + +import ir.PrimitiveTypes._ +import ir.Literals._ +import ir.Operators._ + +import genc.{ CAST => C } +import LIR._ + +import collection.mutable.{ Map => MutableMap, Set => MutableSet } + +private[genc] object IR2CPhase extends TimedLeonPhase[LIR.ProgDef, CAST.Prog] { + val name = "CASTer" + val description = "Translate the IR tree into the final C AST" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("LIR -> CAST") + + def apply(ctx: LeonContext, ir: LIR.ProgDef): CAST.Prog = new IR2CImpl(ctx)(ir) +} + +// This implementation is basically a Transformer that produce something which isn't an IR tree. +// So roughtly the same visiting scheme is applied. +// +// Function conversion is pretty straighforward at this point of the pipeline. Expression conversion +// require little care. But class conversion is harder; see detailed procedure below. +private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { + def apply(ir: ProgDef): C.Prog = rec(ir) + + // We use a cache to keep track of the C function, struct, ... + private val funCache = MutableMap[FunDef, C.Fun]() + private val structCache = MutableMap[ClassDef, C.Struct]() + private val unionCache = MutableMap[ClassDef, C.Union]() // For top hierarchy classes only! + private val enumCache = MutableMap[ClassDef, C.Enum]() // For top hierarchy classes only! + private val arrayCache = MutableMap[ArrayType, C.Struct]() + private val includes = MutableSet[C.Include]() + private val typdefs = MutableSet[C.Typedef]() + + private var dataTypes = Seq[C.DataType]() // For struct & union, keeps track of definition order! + + private def register(dt: C.DataType) { + require(!(dataTypes contains dt)) + + dataTypes = dataTypes :+ dt + } + + private def register(i: C.Include) { + includes += i + } + + private def register(td: C.Typedef) { + typdefs += td + } + + private def rec(id: Id) = C.Id(id) + + private def rec(prog: ProgDef): C.Prog = { + // Convert all the functions and types of the input program + val finder = new ir.DependencyFinder(LIR) + finder(prog) + + finder.getFunctions foreach rec + finder.getClasses foreach rec + + val main = C.generateMain(prog.entryPoint.returnType == PrimitiveType(Int32Type)) + + val enums = enumCache.values.toSet + val functions = funCache.values.toSet + main + + // Remove "mutability" on includes & typedefs + C.Prog(includes.toSet, typdefs.toSet, enums, dataTypes, functions) + } + + private def rec(fd: FunDef): Unit = funCache.getOrElseUpdate(fd, { + val id = rec(fd.id) + val returnType = rec(fd.returnType) + val params = (fd.ctx ++ fd.params) map rec + val body = rec(fd.body) + + C.Fun(id, returnType, params, body) + }) + + private def rec(fb: FunBody): Either[C.Block, String] = fb match { + case FunBodyAST(body) => + Left(C.buildBlock(rec(body))) + + case FunBodyManual(includes, body) => + includes foreach { i => register(C.Include(i)) } + Right(body) + } + + // Yet, we do nothing here. The work is done when converting ValDef types, for example. + private def rec(cd: ClassDef): Unit = () + + private def rec(vd: ValDef): C.Var = { + // TODO Add const whenever possible, based on e.g. vd.isVar + val typ = rec(vd.getType) + val id = rec(vd.id) + C.Var(id, typ) + } + + private def rec(typ: Type): C.Type = typ match { + case PrimitiveType(pt) => C.Primitive(pt) + case ClassType(clazz) => convertClass(clazz) // can be a struct or an enum + case array @ ArrayType(_) => array2Struct(array) + case ReferenceType(t) => C.Pointer(rec(t)) + + case TypedefType(original, alias, include) => + include foreach { i => register(C.Include(i)) } + val td = C.Typedef(rec(original), rec(alias)) + register(td) + td + + case DroppedType => ??? // void*? + case NoType => ??? + } + + // One major difference of this rec compared to Transformer.rec(Expr) is that here + // we don't necessarily follow every branch of the AST. For example, we don't recurse + // on function definitions, hence no problem with recursive functions. + private def rec(e: Expr): C.Expr = e match { + case Binding(vd) => C.Binding(rec(vd.id)) + + case Block(exprs) => C.buildBlock(exprs map rec) + + case Decl(vd) => C.Decl(rec(vd.id), rec(vd.getType)) + + case DeclInit(vd, ArrayInit(ArrayAllocStatic(arrayType, length, values))) => + val bufferId = C.FreshId("buffer") + val bufferDecl = C.DeclArrayStatic(bufferId, rec(arrayType.base), length, values map rec) + val data = C.Binding(bufferId) + val len = C.Lit(IntLit(length)) + val array = array2Struct(arrayType) + val varInit = C.StructInit(array, data :: len :: Nil) + val varDecl = C.DeclInit(rec(vd.id), array, varInit) + + C.buildBlock(bufferDecl :: varDecl :: Nil) + + case DeclInit(vd, ArrayInit(ArrayAllocVLA(arrayType, length, valueInit))) => + val bufferId = C.FreshId("buffer") + val lenId = C.FreshId("length") + val lenDecl = C.DeclInit(lenId, C.Primitive(Int32Type), rec(length)) // Eval `length` once only + val len = C.Binding(lenId) + val bufferDecl = C.DeclArrayVLA(bufferId, rec(arrayType.base), len, rec(valueInit)) + val data = C.Binding(bufferId) + val array = array2Struct(arrayType) + val varInit = C.StructInit(array, data :: len :: Nil) + val varDecl = C.DeclInit(rec(vd.id), array, varInit) + + C.buildBlock(lenDecl :: bufferDecl :: varDecl :: Nil) + + case DeclInit(vd, value) => C.DeclInit(rec(vd.id), rec(vd.getType), rec(value)) + + case App(fd, extra, args) => C.Call(rec(fd.id), (extra ++ args) map rec) + + case Construct(cd, args) => constructObject(cd, args) // can be a StructInit or an EnumLiteral + + case ArrayInit(alloc) => internalError("This should be part of a DeclInit expression!") + + case FieldAccess(objekt, fieldId) => C.FieldAccess(rec(objekt), rec(fieldId)) + case ArrayAccess(array, index) => C.ArrayAccess(C.FieldAccess(rec(array), C.Id("data")), rec(index)) + case ArrayLength(array) => C.FieldAccess(rec(array), C.Id("length")) + + case Assign(lhs, rhs) => C.Assign(rec(lhs), rec(rhs)) + case BinOp(op, lhs, rhs) => C.BinOp(op, rec(lhs), rec(rhs)) + case UnOp(op, expr) => C.UnOp(op, rec(expr)) + + case If(cond, thenn) => C.If(rec(cond), C.buildBlock(rec(thenn))) + case IfElse(cond, thenn, elze) => C.IfElse(rec(cond), C.buildBlock(rec(thenn)), C.buildBlock(rec(elze))) + case While(cond, body) => C.While(rec(cond), C.buildBlock(rec(body))) + + // Find out if we can actually handle IsInstanceOf. + case IsA(_, ClassType(cd)) if cd.parent.isEmpty => C.True // Since it has typecheck, it can only be true. + + // Currently, classes are tagged with a unique ID per class hierarchy, but + // without any kind of ordering. This means we cannot have test for membership + // but only test on concrete children is supported. We could improve on that + // using something similar to Cohen's encoding. + case IsA(_, ClassType(cd)) if cd.isAbstract => + internalError("Cannot handle membership test with abstract types for now") + + case IsA(expr, ct) => + val tag = getEnumLiteralFor(ct.clazz) + val tagAccess = C.FieldAccess(rec(expr), TaggedUnion.tag) + C.BinOp(Equals, tagAccess, tag) + + case AsA(expr, ClassType(cd)) if cd.parent.isEmpty => rec(expr) // no casting, actually + + case AsA(expr, ClassType(cd)) if cd.isAbstract => + internalError("Cannot handle cast with abstract types for now") + + case AsA(expr, ct) => + val fieldId = getUnionFieldFor(ct.clazz) + val unionAccess = C.FieldAccess(rec(expr), TaggedUnion.value) + C.FieldAccess(unionAccess, fieldId) + + case Lit(lit) => C.Lit(lit) + + case Ref(e) => C.Ref(rec(e)) + case Deref(e) => C.Deref(rec(e)) + + case Return(e) => C.Return(rec(e)) + case Break => C.Break + } + + // Construct an instantce of the given case class. + // + // There are three main cases: + // - 1) This case class has no parent; + // - 2) This class is part of a class hierarchy and some of the leaves classes have fields; + // - 3) This class is part of a class hierarchy but none of the concrete classes have fields. + // + // In the first case, we can "simply" construct the structure associated with this case class. + // + // In the second case, we need to construct the structure representing the class hierarchy with + // its tag (an enumeration representing the runtime type of the object) and its value (an union + // containing the relevant bits of data for the runtime type of the object). + // + // In the third and final case, we can express the whole class hierarchy using only an enumeration + // to improve both memory space and execution speed. + // + // This function is just a proxy for the three main cases implementations. + // + // NOTE Empty classes are not supported in pure C99 (GNU-C99 does) so we have to add a dummy byte + // field. (This is how it is implemented in GCC.) + // + // NOTE For scenarios 2) we consider the whole class hierarchy even though in some contexts + // we could work with a subset. This leaves room for improvement, such as in the following + // (simplified) example: + // + // abstract class GrandParent + // case class FirstChild(...) extends GrandParent + // abstract class Parent extends GrandParent + // case class GrandChildren1(...) extends Parent + // case class GrandChildren2(...) extends Parent + // + // def foo(p: Parent) = ... + // def bar = foo(GrandChildren2(...)) + // + // In this example, `p` could hold information only about either GrandChildren1 or GrandChildren2 + // but the current implementation will treat it as if were a GrandParent. + // + // In order to implement such optimisation, we would need to keep track of which minimal "level" + // of the hierarchy is required. This might also involve playing around with the how methods + // are extracted wihtin Leon because a method defined on, say, Parent will be extracted as a + // function taking a GrandParent as parameter (with an extra pre-condition requiring that + // the parameter is an instance of Parent). + // + // NOTE Leon guarantees us that every abstract class has at least one (indirect) case class child. + // + private def constructObject(cd: ClassDef, args: Seq[Expr]): C.Expr = { + require(!cd.isAbstract) + + val leaves = cd.getHierarchyLeaves // only leaves have fields + if (leaves exists { cd => cd.fields.nonEmpty }) { + if (cd.parent.isEmpty) simpleConstruction(cd, args) + else hierarchyConstruction(cd, args) + } else enumerationConstruction(cd, args) + } + + private def convertClass(cd: ClassDef): C.Type = { + val leaves = cd.getHierarchyLeaves + if (leaves exists { cd => cd.fields.nonEmpty }) getStructFor(cd) + else getEnumFor(cd.hierarchyTop) + } + + private val markedAsEmpty = MutableSet[ClassDef]() + + private def markAsEmpty(cd: ClassDef) { + markedAsEmpty += cd + } + + private def simpleConstruction(cd: ClassDef, args0: Seq[Expr]): C.StructInit = { + // Ask for the C structure associated with cd + val struct = getStructFor(cd) + + // Check whether an extra byte was added to the structure + val args = + if (markedAsEmpty(cd)) Seq(Lit(IntLit(0))) + else args0 + + C.StructInit(struct, args map rec) + } + + private def hierarchyConstruction(cd: ClassDef, args: Seq[Expr]): C.StructInit = { + // Build the structure wrapper for tagged union + val topStruct = getStructFor(cd.hierarchyTop) + val union = getUnionFor(cd.hierarchyTop) + val tag = getEnumLiteralFor(cd) + val unionField = getUnionFieldFor(cd) + val childInit = simpleConstruction(cd, args) + val unionInit = C.UnionInit(union, unionField, childInit) + + C.StructInit(topStruct, Seq(tag, unionInit)) + } + + private def enumerationConstruction(cd: ClassDef, args: Seq[Expr]): C.EnumLiteral = { + if (args.nonEmpty) + internalError("Enumeration should have no construction arguments!") + + getEnumLiteralFor(cd) + } + + // Extract from cache, or build the C structure for the given class definition. + // + // Here are the three cases we can be in: + // 1) the given class definition is a case class; + // 2) it is the top class of a class hierarchy; + // 3) it is an abstract class inside the class hierarchy. + // + // NOTE As described in a NOTE above, scenarios 2) & 3) are not yet distinguished. + // We currently treat case 3) as 2). + private def getStructFor(cd: ClassDef): C.Struct = { + val candidate = if (cd.isAbstract) cd.hierarchyTop else cd + + structCache get candidate match { + case None => + val struct = + if (candidate.isAbstract) buildStructForHierarchy(candidate) + else buildStructForCaseClass(candidate) + + // Register the struct in the class cache AND as a datatype + structCache.update(candidate, struct) + register(struct) + + struct + + case Some(struct) => struct + } + } + + // Build the union used in the "tagged union" structure that is representing a class hierarchy. + private def getUnionFor(cd: ClassDef): C.Union = unionCache.getOrElseUpdate(cd, { + require(cd.isAbstract && cd.parent.isEmpty) // Top of hierarchy + + // List all (concrete) leaves of the class hierarchy as fields of the union. + val leaves = cd.getHierarchyLeaves + val fields = leaves.toSeq map { c => C.Var(getUnionFieldFor(c), getStructFor(c)) } + val id = rec("union_" + cd.id) + + val union = C.Union(id, fields) + + // Register the union as a datatype as well + register(union) + + union + }) + + // Build the enum used in the "tagged union" structure that is representing a class hierarchy. + private def getEnumFor(cd: ClassDef): C.Enum = enumCache.getOrElseUpdate(cd, { + // List all (concrete) leaves of the class hierarchy as fields of the union. + val leaves = cd.getHierarchyLeaves + val literals = leaves.toSeq map getEnumLiteralFor + val id = rec("enum_" + cd.id) + + C.Enum(id, literals) + }) + + // Get the tagged union field id for a leaf + private def getUnionFieldFor(cd: ClassDef) = C.Id(cd.id + "_v") + + // Get the tag id for a leaf + private def getEnumLiteralFor(cd: ClassDef) = C.EnumLiteral(C.Id("tag_" + cd.id)) + + // Build a tagged union for the class hierarchy + private def buildStructForHierarchy(top: ClassDef): C.Struct = { + val tagType = getEnumFor(top) + val tag = C.Var(TaggedUnion.tag, tagType) + + val unionType = getUnionFor(top) + val union = C.Var(TaggedUnion.value, unionType) + + C.Struct(rec(top.id), tag :: union :: Nil) + } + + private def buildStructForCaseClass(cd: ClassDef): C.Struct = { + // Here the mapping is straightforward: map the class fields, + // possibly creating a dummy one to avoid empty classes. + val fields = if (cd.fields.isEmpty) { + warning(s"Empty structures are not allowed according to the C99 standard. " + + s"I'm adding a dummy byte to ${cd.id} structure for compatibility purposes.") + markAsEmpty(cd) + Seq(C.Var(C.Id("extra"), C.Primitive(CharType))) + } else cd.fields map rec + + C.Struct(rec(cd.id), fields) + } + + private object TaggedUnion { + val tag = C.Id("tag") + val value = C.Id("value") + } + + // Create a structure that will contain a data and length member to nicely wrap an array + private def array2Struct(arrayType: ArrayType): C.Struct = arrayCache.getOrElseUpdate(arrayType, { + val length = C.Var(Array.length, C.Primitive(Int32Type)) // Add const + val base = rec(arrayType.base) + val data = C.Var(Array.data, C.Pointer(base)) + val id = C.Id(repId(arrayType)) + + val array = C.Struct(id, data :: length :: Nil) + + // This needs to get registered as a datatype as well + register(array) + + array + }) + + private object Array { + val length = C.Id("length") + val data = C.Id("data") + } + +} + diff --git a/src/main/scala/leon/genc/phases/LiftingPhase.scala b/src/main/scala/leon/genc/phases/LiftingPhase.scala new file mode 100644 index 000000000..71d3ab2ab --- /dev/null +++ b/src/main/scala/leon/genc/phases/LiftingPhase.scala @@ -0,0 +1,35 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import ir.IRs.{ NIR, LIR } +import ir.{ ClassLifter } + +/* + * This phase lifts class types to their top level class and add appropriate + * AsA cast to make sure the output program works on the same inputs. + * + * This is done in order to use tagged union to represent classes in C. + */ +private[genc] object LiftingPhase extends TimedLeonPhase[NIR.ProgDef, LIR.ProgDef] { + val name = "Lifter" + val description = "Lift class types to their hierarchy top class" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("NIR -> LIR") + + def apply(ctx: LeonContext, nprog: NIR.ProgDef): LIR.ProgDef = { + val reporter = MiniReporter(ctx) + import reporter._ + + val referencing = new ClassLifter(ctx) + val lprog = referencing(nprog) + + debugTree("RESUTING LIR PROGRAM", lprog) + + lprog + } +} + + diff --git a/src/main/scala/leon/genc/phases/NormalisationPhase.scala b/src/main/scala/leon/genc/phases/NormalisationPhase.scala new file mode 100644 index 000000000..68b5cac10 --- /dev/null +++ b/src/main/scala/leon/genc/phases/NormalisationPhase.scala @@ -0,0 +1,33 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import ir.IRs.{ RIR, NIR } +import ir.{ Normaliser } + +/* + * Normalise the program by adding explicit execution points and making sure + * argument-like expressions are "simple" expressions (and not e.g. blocks). + */ +private[genc] object NormalisationPhase extends TimedLeonPhase[RIR.ProgDef, NIR.ProgDef] { + val name = "Normaliser" + val description = "Normalise IR to match the C execution model" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("RIR -> NIR") + + def apply(ctx: LeonContext, rprog: RIR.ProgDef): NIR.ProgDef = { + val reporter = MiniReporter(ctx) + import reporter._ + + val normaliser = new Normaliser(ctx) + val nprog = normaliser(rprog) + + debugTree("RESUTING NIR PROGRAM", nprog) + + nprog + } +} + + diff --git a/src/main/scala/leon/genc/phases/ReferencingPhase.scala b/src/main/scala/leon/genc/phases/ReferencingPhase.scala new file mode 100644 index 000000000..c6c759c5d --- /dev/null +++ b/src/main/scala/leon/genc/phases/ReferencingPhase.scala @@ -0,0 +1,37 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import ir.IRs.{ CIR, RIR } +import ir.{ Referentiator } + +/* + * This phase identify which variable should be reference instead of value, + * and make sure reference are dereferenced before being accessed. + * + * Add ReferenceType, Ref and Deref to the input CIR program. + * + * NOTE a ReferenceType(T) is basically a T* in C. + */ +private[genc] object ReferencingPhase extends TimedLeonPhase[CIR.ProgDef, RIR.ProgDef] { + val name = "Referencer" + val description = "Add 'referencing' to the input CIR program to produce a RIR program" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("CIR -> RIR") + + def apply(ctx: LeonContext, cprog: CIR.ProgDef): RIR.ProgDef = { + val reporter = MiniReporter(ctx) + import reporter._ + + val referencing = new Referentiator(ctx) + val rprog = referencing(cprog) + + debugTree("RESUTING RIR PROGRAM", rprog) + + rprog + } +} + + diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala new file mode 100644 index 000000000..55ce1c8c0 --- /dev/null +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -0,0 +1,524 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc +package phases + +import purescala.Common._ +import purescala.Definitions._ +import purescala.Expressions._ +import purescala.{ ExprOps } +import purescala.Types._ +import xlang.Expressions._ + +import ExtraOps._ + +import ir.{ PrimitiveTypes => PT, Literals => L, Operators => O } +import ir.IRs.{ CIR } + +import scala.collection.mutable.{ Map => MutableMap } + +/* + * This phase takes a set of definitions (the Dependencies) and the fonction context database (FunCtxDB) + * and produces an equivalent program expressed in the intermediate representation without generic types (CIR). + */ +private[genc] object Scala2IRPhase extends TimedLeonPhase[(Dependencies, FunCtxDB), CIR.ProgDef] { + val name = "Scala to IR converter" + val description = "Convert the Scala AST into GenC's 'generic' IR" + + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("Scala -> CIR") + + def apply(ctx: LeonContext, input: (Dependencies, FunCtxDB)): CIR.ProgDef = { + val reporter = MiniReporter(ctx) + import reporter._ + + val (deps, ctxDB) = input + val entryPoint = getEntryPoint(deps) + + val impl = new S2IRImpl(ctx, ctxDB, deps) + val ir = CIR.ProgDef(impl(entryPoint)) + + debugTree("RESULTING CIR", ir) + + ir + } + + private def getEntryPoint(deps: Set[Definition]): FunDef = { + val opt = deps collectFirst { + case fd: FunDef if fd.id.name == "_main" => fd + } + + opt.get // If not defined, the previous phase did something wrong. + } + +} + + +private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Dependencies) extends MiniReporter { + + /**************************************************************************************************** + * Entry point of conversion * + ****************************************************************************************************/ + def apply(entryPoint: FunDef): CIR.FunDef = rec(entryPoint.typed) + + + + /**************************************************************************************************** + * Caches * + ****************************************************************************************************/ + private val funCache = MutableMap[TypedFunDef, CIR.FunDef]() + private val classCache = MutableMap[ClassType, CIR.ClassDef]() + + + + /**************************************************************************************************** + * Helper functions * + ****************************************************************************************************/ + private def convertVarInfoToArg(vi: VarInfo) = CIR.ValDef(rec(vi.id), rec(vi.typ), vi.isVar) + private def convertVarInfoToParam(vi: VarInfo) = CIR.Binding(convertVarInfoToArg(vi)) + + // Extract the ValDef from the known one + private def buildBinding(id: Identifier)(implicit env: Env) = CIR.Binding(env(id)) + + private def buildLet(x: Identifier, e: Expr, body: Expr, isVar: Boolean)(implicit env: Env): CIR.Expr = { + val vd = CIR.ValDef(rec(x), rec(x.getType), isVar) + val decl = CIR.DeclInit(vd, rec(e)) + val newEnv = env + (x -> vd) + val rest = rec(body)(newEnv) + + CIR.buildBlock(Seq(decl, rest)) + } + + private def buildId(tfd: TypedFunDef): CIR.Id = + rec(tfd.fd.id) + buildIdPostfix(tfd.tps) + + private def buildId(ct: ClassType): CIR.Id = + rec(ct.classDef.id) + buildIdPostfix(ct.tps) + + private def buildIdPostfix(tps: Seq[TypeTree]): CIR.Id = if (tps.isEmpty) "" else { + "_" + (tps filterNot { _ == Untyped } map rec map CIR.repId mkString "_") + } + + private def buildBinOp(lhs: Expr, op: O.BinaryOperator, rhs: Expr)(implicit env: Env) = + CIR.BinOp(op, rec(lhs), rec(rhs)) + + private def buildUnOp(op: O.UnaryOperator, expr: Expr)(implicit env: Env) = + CIR.UnOp(op, rec(expr)) + + // Create a binary AST + private def buildMultiOp(op: O.BinaryOperator, exprs: Seq[Expr])(implicit env: Env): CIR.BinOp = exprs.toList match { + case Nil => internalError("no operands") + case a :: Nil => internalError("at least two operands required") + case a :: b :: Nil => CIR.BinOp(op, rec(a), rec(b)) + case a :: xs => CIR.BinOp(op, rec(a), buildMultiOp(op, xs)) + } + + // Tuples are converted to classes + private def tuple2Class(typ: TypeTree): CIR.ClassDef = typ match { + case TupleType(bases) => + val types = bases map rec + val fields = types.zipWithIndex map { case (typ, i) => CIR.ValDef("_" + (i+1), typ, isVar = false) } + val id = "Tuple" + buildIdPostfix(bases) + CIR.ClassDef(id, None, fields, isAbstract = false) + + case _ => internalError("Unexpected ${typ.getClass} instead of TupleType") + } + + // When converting expressions, we keep track of the variable in scope to build Bindings + type Env = Map[Identifier, CIR.ValDef] + + + + /**************************************************************************************************** + * Pattern Matching helper functions * + ****************************************************************************************************/ + // NOTE We don't rely on ExprOps.matchToIfThenElse because it doesn't apply well with + // side-effects and type casting. + + private case class PMCase(cond: CIR.Expr, guardOpt: Option[CIR.Expr], body: CIR.Expr) { + def fullCondition: CIR.Expr = guardOpt match { + case None => cond + case Some(guard) if cond == CIR.True => guard + case Some(guard) => CIR.BinOp(O.And, cond, guard) + } + } + + private object ElseCase { + def unapply(caze: PMCase): Option[CIR.Expr] = { + if (CIR.True == caze.fullCondition) Some(caze.body) + else None + } + } + + private def convertPatMap(scrutinee0: Expr, cases0: Seq[MatchCase])(implicit env: Env): CIR.Expr = { + require(cases0.nonEmpty) + + def withTmp(typ: TypeTree, value: Expr, env: Env): (Variable, Some[CIR.DeclInit], Env) = { + val tmp0 = FreshIdentifier("tmp", typ) + val tmpId = rec(tmp0) + val tmpTyp = rec(typ) + val tmp = CIR.ValDef(tmpId, tmpTyp, isVar = false) + val pre = CIR.DeclInit(tmp, rec(value)(env)) + + (tmp0.toVariable, Some(pre), env + (tmp0 -> tmp)) + } + + val (scrutinee, preOpt, newEnv) = scrutinee0 match { + case v: Variable => (v, None, env) + case Block(Nil, v: Variable) => (v, None, env) + case Block(init, v: Variable) => (v, Some(rec(Block(init.init, init.last))), env) + + case fi @ FunctionInvocation(_, _) => withTmp(scrutinee0.getType, fi, env) + case cc @ CaseClass(_, _) => withTmp(scrutinee0.getType, cc, env) + + case e => internalError(s"scrutinee = $e of type ${e.getClass} is not supported") + } + + val cases = cases0 map { caze => convertCase(scrutinee, caze)(newEnv) } + + // Identify the last case + val last = cases.last match { + case ElseCase(body) => body + case caze => CIR.If(caze.fullCondition, caze.body) + } + + // Combine all cases, using a foldRight + val patmat = (cases.init :\ last) { case (caze, acc) => + CIR.IfElse(caze.fullCondition, caze.body, acc) + } + + preOpt match { + case None => patmat + case Some(pre) => CIR.buildBlock(pre :: patmat :: Nil) + } + } + + // Substitute a binder (id) by the scrutinee (or a more appropriate expression) in the given expression + private def substituteBinder(id: Identifier, replacement: Expr, expr: Expr): Expr = + ExprOps.replaceFromIDs(Map(id -> replacement), expr) + + private def buildConjunction(exprs: Seq[CIR.Expr]): CIR.Expr = exprs.foldRight[CIR.Expr](CIR.True) { case (e, acc) => + if (e == CIR.True) acc // Don't add trivialities in the conjunction + else if (acc == CIR.True) e + else CIR.BinOp(O.And, e, acc) + } + + // Extract the condition, guard and body (rhs) of a match case + private def convertCase(initialScrutinee: Expr, caze: MatchCase)(implicit env: Env): PMCase = { + // We need to keep track of binder (and binders in sub-patterns) and their appropriate + // substitution. We do so in an Imperative manner with variables -- sorry FP, but it's + // much simpler that way! However, to encapsulate things a bit, we use the `update` + // function to update both the guard and rhs safely. When we have finished converting + // every cases, we'll be able to convert the guard and rhs to IR. + var guardOpt = caze.optGuard + var body = caze.rhs + + def update(binderOpt: Option[Identifier], replacement: Expr): Unit = binderOpt match { + case Some(binder) => + guardOpt = guardOpt map { guard => substituteBinder(binder, replacement, guard) } + body = substituteBinder(binder, replacement, body) + + case None => () + } + + // For the main pattern and its subpatterns, we keep track of the "current" scrutinee + // expression (after cast, field access, and other similar operations). + def ccRec(pat: Pattern, scrutinee: Expr): CIR.Expr = pat match { + case InstanceOfPattern(b, ct) => + val cast = AsInstanceOf(scrutinee, ct) + update(b, cast) + + rec(IsInstanceOf(scrutinee, ct)) + + case WildcardPattern(b) => + update(b, scrutinee) + CIR.True + + case CaseClassPattern(b, ct, subs) => + val cast = AsInstanceOf(scrutinee, ct) + update(b, cast) + + val checkType = rec(IsInstanceOf(scrutinee, ct)) + // Use the classDef fields to have the original identifiers! + val checkSubs = (ct.classDef.fields zip subs) map { case (field, sub) => + ccRec(sub, CaseClassSelector(ct, cast, field.id)) + } + + // Luckily, there are no block involved so we can have a simple conjunction + buildConjunction(checkType +: checkSubs) + + case TuplePattern(b, subs) => + // Somewhat similar to CaseClassPattern, but simpler + update(b, scrutinee) + + val checkSubs = subs.zipWithIndex map { case (sub, index) => + ccRec(sub, TupleSelect(scrutinee, index+1)) + } + + buildConjunction(checkSubs) + + case LiteralPattern(b, lit) => + update(b, scrutinee) + buildBinOp(scrutinee, O.Equals, lit) + + case UnapplyPattern(bind, unapply, subs) => + fatalError(s"Unapply Pattern, a.k.a. Extractor Objects", pat.getPos) + } + + val cond = ccRec(caze.pattern, initialScrutinee) + + PMCase(cond, guardOpt map rec, rec(body)) + } + + + + /**************************************************************************************************** + * Recursive conversion * + ****************************************************************************************************/ + private def rec(id: Identifier): CIR.Id = { + if (id.name == "_main") "_main" + else { + val uniqueId = id.uniqueNameDelimited("_") + if (uniqueId endsWith "_1") id.name // when possible, keep the id as clean as possible + else uniqueId + } + + } + + // Try first to fetch the function from cache to handle recursive funcitons. + private def rec(tfd: TypedFunDef): CIR.FunDef = funCache get tfd getOrElse { + val id = buildId(tfd) + + // Make sure to get the id from the function definition, not the typed one, as they don't always match. + val paramTypes = tfd.params map { p => rec(p.getType) } + val paramIds = tfd/*.fd*/.params map { p => rec(p.id) } + val params = (paramIds zip paramTypes) map { case (id, typ) => CIR.ValDef(id, typ, isVar = false) } + + // Extract the context for the function definition. + val ctx = ctxDB(tfd.fd) map convertVarInfoToArg + // TODO THERE MIGHT BE SOME GENERICS IN THE CONTEXT!!! + + val returnType = rec(tfd.returnType) + + // Build a partial function without body in order to support recursive functions + val fun = CIR.FunDef(id, returnType, ctx, params, null) + funCache.update(tfd, fun) + + // Now proceed with the body + val body: CIR.FunBody = + if (tfd.fd.isManuallyDefined) { + val impl = tfd.fd.getManualDefinition + CIR.FunBodyManual(impl.includes, impl.code) + } else { + val ctxEnv = (ctxDB(tfd.fd) map { _.id }) zip ctx + val paramEnv = (tfd/*.fd*/.params map { _.id }) zip params + val env = (ctxEnv ++ paramEnv).toMap + + // TODO Find out if this is an issue or not by writing a regression test. + warning(s"we are invalidating the ctx names because we are using the translated version of the body of $id") + + CIR.FunBodyAST(rec(tfd.fullBody)(env)) + } + + // Now that we have a body, we can fully build the FunDef + fun.body = body + + fun + } + + private def rec(typ: TypeTree): CIR.Type = typ match { + case UnitType => CIR.PrimitiveType(PT.UnitType) + case BooleanType => CIR.PrimitiveType(PT.BoolType) + case Int32Type => CIR.PrimitiveType(PT.Int32Type) + case CharType => CIR.PrimitiveType(PT.CharType) + case StringType => CIR.PrimitiveType(PT.StringType) + + // case tp @ TypeParameter(_) => CIR.ParametricType(convertId(tp.id)) + + // For both case classes and abstract classes: + case ct: ClassType => + val cd = ct.classDef + if (cd.isDropped) { + CIR.DroppedType + } else if (cd.isManuallyTyped) { + val typedef = cd.getManualType + CIR.TypedefType(cd.id.name, typedef.alias, typedef.include) + } else { + CIR.ClassType(rec(ct)) + } + + case ArrayType(base) => CIR.ArrayType(rec(base)) + + case TupleType(_) => CIR.ClassType(tuple2Class(typ)) + + case FunctionType(from, to) => internalError(s"what shoud I do with $from -> $to") + + case t => internalError(s"type tree of type ${t.getClass} not handled") + } + + private def rec(ct: ClassType): CIR.ClassDef = { + // Convert the whole class hierarchy to register all siblings, in a top down fasion, that way + // each children class in the the CIR hierarchy get registered to its parent and we can keep track + // of all of them. + + type Translation = Map[ClassType, CIR.ClassDef] + + def recTopDown(ct: ClassType, parent: Option[CIR.ClassDef], acc: Translation): Translation = { + if (ct.classDef.isDropped || ct.classDef.isManuallyTyped) + internalError(s"${ct.id} is not convertible to ClassDef!") + + val id = buildId(ct) + assert(!ct.classDef.isCaseObject) + + // Use the class definition id, not the typed one as they might not match. + val fieldTypes = ct.fieldsTypes map rec + val fields = (ct.classDef.fields zip fieldTypes) map { case (vd, typ) => CIR.ValDef(rec(vd.id), typ, vd.isVar) } + + val clazz = CIR.ClassDef(id, parent, fields, ct.classDef.isAbstract) + + val newAcc = acc + (ct -> clazz) + + // Recurse down + val children = ct.classDef.knownChildren map { _.typed(ct.tps) } + (newAcc /: children) { case (currentAcc, child) => recTopDown(child, Some(clazz), currentAcc) } + } + + val translation = recTopDown(ct.root, None, Map.empty) + + translation(ct) + } + + private def rec(e: Expr)(implicit env: Env): CIR.Expr = e match { + case UnitLiteral() => CIR.Lit(L.UnitLit) + case BooleanLiteral(v) => CIR.Lit(L.BoolLit(v)) + case IntLiteral(v) => CIR.Lit(L.IntLit(v)) + case CharLiteral(v) => CIR.Lit(L.CharLit(v)) + case StringLiteral(v) => CIR.Lit(L.StringLit(v)) + + case Block(es, last) => CIR.buildBlock((es :+ last) map rec) + + case Variable(id) => buildBinding(id) + + case Let(x, e, body) => buildLet(x, e, body, isVar = false) + case LetVar(x, e, body) => buildLet(x, e, body, isVar = true) + + case Assignment(id, expr) => CIR.Assign(buildBinding(id), rec(expr)) + + case FieldAssignment(obj, fieldId, expr) => + CIR.Assign(CIR.FieldAccess(rec(obj), rec(fieldId)), rec(expr)) + + case LetDef(_, body) => + // We don't have to traverse the nested function now because we already have their respective context. + rec(body) + + case FunctionInvocation(tfd, args0) => + val fun = rec(tfd) + val extra = ctxDB(tfd.fd) map convertVarInfoToParam + val args = args0 map rec + + CIR.App(fun, extra, args) + + case CaseClass(cct, args0) => + val clazz = rec(cct) + val args = args0 map rec + + CIR.Construct(clazz, args) + + case CaseClassSelector(_, obj, fieldId) => CIR.FieldAccess(rec(obj), rec(fieldId)) + + case tuple @ Tuple(args0) => + val clazz = tuple2Class(tuple.getType) + val args = args0 map rec + + CIR.Construct(clazz, args) + + case TupleSelect(tuple, idx) => + CIR.FieldAccess(rec(tuple), "_" + idx) + + case ArrayLength(array) => CIR.ArrayLength(rec(array)) + + case ArraySelect(array, index) => CIR.ArrayAccess(rec(array), rec(index)) + + case ArrayUpdate(array, index, value) => + CIR.Assign(CIR.ArrayAccess(rec(array), rec(index)), rec(value)) + + case array @ NonemptyArray(empty, Some((value0, length0))) if empty.isEmpty => + val arrayType = rec(array.getType).asInstanceOf[CIR.ArrayType] + val value = rec(value0) + + // Convert to VLA or normal array + val alloc = rec(length0) match { + case CIR.Lit(L.IntLit(length)) => + val values = (0 until length) map { _ => value } // the same expression, != same runtime value + CIR.ArrayAllocStatic(arrayType, length, values) + + case length => + if (arrayType.base.containsArray) + fatalError(s"VLAs cannot have elements being/containing other array", array.getPos) + + warning(s"VLAs should be avoid according to MISRA C rules", array.getPos) + + CIR.ArrayAllocVLA(arrayType, length, value) + } + + CIR.ArrayInit(alloc) + + + case NonemptyArray(_, Some(_)) => + internalError("Inconsistent state of NonemptyArray") + + + case array @ NonemptyArray(elems, None) => // Here elems is non-empty + val arrayType = rec(array.getType).asInstanceOf[CIR.ArrayType] + + // Sort values according the the key (aka index) + val indexes = elems.keySet.toSeq.sorted + val values = indexes map { i => rec(elems(i)) } + + // Assert all types are the same + if (values exists { _.getType != arrayType.base }) + fatalError("Heterogenous arrays", array.getPos) + + val alloc = CIR.ArrayAllocStatic(arrayType, values.length, values) + CIR.ArrayInit(alloc) + + + case IfExpr(cond, thenn, NoTree(_)) => CIR.If(rec(cond), rec(thenn)) + case IfExpr(cond, thenn, elze) => CIR.IfElse(rec(cond), rec(thenn), rec(elze)) + + case While(cond, body) => CIR.While(rec(cond), rec(body)) + + case LessThan(lhs, rhs) => buildBinOp(lhs, O.LessThan, rhs) + case GreaterThan(lhs, rhs) => buildBinOp(lhs, O.GreaterThan, rhs) + case LessEquals(lhs, rhs) => buildBinOp(lhs, O.LessEquals, rhs) + case GreaterEquals(lhs, rhs) => buildBinOp(lhs, O.GreaterEquals, rhs) + case Equals(lhs, rhs) => buildBinOp(lhs, O.Equals, rhs) + case Not(Equals(lhs, rhs)) => buildBinOp(lhs, O.NotEquals, rhs) + + case Not(rhs) => buildUnOp(O.Not, rhs) + case And(exprs) => buildMultiOp(O.And, exprs) + case Or(exprs) => buildMultiOp(O.Or, exprs) + + case BVPlus(lhs, rhs) => buildBinOp(lhs, O.Plus, rhs) + case BVMinus(lhs, rhs) => buildBinOp(lhs, O.Minus, rhs) + case BVUMinus(rhs) => buildUnOp(O.UMinus, rhs) + case BVTimes(lhs, rhs) => buildBinOp(lhs, O.Times, rhs) + case BVDivision(lhs, rhs) => buildBinOp(lhs, O.Div, rhs) + case BVRemainder(lhs, rhs) => buildBinOp(lhs, O.Modulo, rhs) + case BVNot(rhs) => buildUnOp(O.BNot, rhs) + case BVAnd(lhs, rhs) => buildBinOp(lhs, O.BAnd, rhs) + case BVOr(lhs, rhs) => buildBinOp(lhs, O.BOr, rhs) + case BVXOr(lhs, rhs) => buildBinOp(lhs, O.BXor, rhs) + case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, O.BLeftShift, rhs) + case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs) + case BVLShiftRight(lhs, rhs) => fatalError("Operator >>> is not supported", e.getPos) + + case MatchExpr(scrutinee, cases) => convertPatMap(scrutinee, cases) + case IsInstanceOf(expr, ct) => CIR.IsA(rec(expr), CIR.ClassType(rec(ct))) + case AsInstanceOf(expr, ct) => CIR.AsA(rec(expr), CIR.ClassType(rec(ct))) + + case e => internalError(s"expression `$e` of type ${e.getClass} not handled") + } + +} + diff --git a/src/main/scala/leon/genc/phases/package.scala b/src/main/scala/leon/genc/phases/package.scala new file mode 100644 index 000000000..43ea116a9 --- /dev/null +++ b/src/main/scala/leon/genc/phases/package.scala @@ -0,0 +1,22 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +package leon +package genc + +import purescala.Common.{ Identifier } +import purescala.Definitions.{ Definition, FunDef, ValDef } +import purescala.Types.{ TypeTree } + +/* + * Some type aliased for readability + */ +package object phases { + + case class VarInfo(id: Identifier, typ: TypeTree, isVar: Boolean) + + type FunCtxDB = Map[FunDef, Seq[VarInfo]] + + type Dependencies = Set[Definition] + +} + diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala index 0ceec31f1..517dbb14d 100644 --- a/src/test/scala/leon/genc/GenCSuite.scala +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -220,10 +220,10 @@ class GenCSuite extends LeonRegressionSuite { private def convert(xCtx: ExtendedContext)(prog: Program): CAST.Prog = { try { info(s"(CAST)${xCtx.progName}") - GenerateCPhase(xCtx.leon, prog) + GenerateCPhase.run(xCtx.leon, prog)._2 } catch { case fe: LeonFatalError => - fail(xCtx.leon, "Convertion to C unexpectedly failed", fe) + fail(xCtx.leon, "Conversion to C unexpectedly failed", fe) } } @@ -350,7 +350,7 @@ class GenCSuite extends LeonRegressionSuite { protected def testInvalid() = forEachFileIn("invalid") { (xCtx, prog) => intercept[LeonFatalError] { - GenerateCPhase(xCtx.leon, prog) + GenerateCPhase.run(xCtx.leon, prog)._2 } } From c31230ac39a57f800bd07e0e77d84ed3a9da73b7 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 10 Nov 2016 11:47:07 +0100 Subject: [PATCH 07/77] Add more regression tests for scope, inheritance and generics --- .../unverified/ComplexNestedGeneric.scala | 29 ++++ .../regression/genc/unverified/Scopes.scala | 132 ++++++++++++++++ .../unverified/UsingConcreteClasses.scala | 22 +++ .../regression/genc/valid/Inheritance9.scala | 143 ++++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 src/test/resources/regression/genc/unverified/ComplexNestedGeneric.scala create mode 100644 src/test/resources/regression/genc/unverified/Scopes.scala create mode 100644 src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala create mode 100644 src/test/resources/regression/genc/valid/Inheritance9.scala diff --git a/src/test/resources/regression/genc/unverified/ComplexNestedGeneric.scala b/src/test/resources/regression/genc/unverified/ComplexNestedGeneric.scala new file mode 100644 index 000000000..a3b0f91e2 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/ComplexNestedGeneric.scala @@ -0,0 +1,29 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object ComplexNestedGeneric { + + def _main() = { + def foo[A](opt: Option[A]) = { + var local: Option[A] = None[A] + + def inner(b: Boolean) { + if (b) local = Some(opt.get) + } + + inner(opt.isDefined) + + local.isDefined + } + + if (foo(None[Int]) == false && foo(Some(true)) == true) 0 + else 1 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/unverified/Scopes.scala b/src/test/resources/regression/genc/unverified/Scopes.scala new file mode 100644 index 000000000..0ba55c7d4 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/Scopes.scala @@ -0,0 +1,132 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object Scopes { + + case class W[T](x: T) + + def woo(x: Int) = W(x) + def woo(x: Boolean) = W(x) + + abstract class Option[T] + case class Some[T](x: T) extends Option[T] + case class None[T]() extends Option[T] + + def opt(x: Int) = Some(x) + def opt(x: Boolean) = Some(x) + + def scope = { + def gen2[A, B](a: A, b: B) = 0 + + def gen1[A](a: A) = gen2(a, a) + + def bar[A](a: A) = gen2(0, a) + + val v = bar(gen1(0)) + } + + def scope2 = { + + def none(x: Int) = x + + val a = 42 + + def withA(x: Int) = none(x + a) + + def alsoWithA(x: Int, y: Int) = withA(x + y) + + val b = 58 + + def withAandB() = none(a + b) + + def nesting() = { + def againWithAandB() = a + b + + val c = 100 + + def withMoreVars(x: Int) = a + b + c + x + + withMoreVars(-100) + } + + val c = 31415926 + + def genericId[T](x: T) = x + + val d = genericId(c) + val e = genericId(true) + + def genericAndComplexIdFunction[T](f: T) = { + def subsubsubsub(g: T) = g + + subsubsubsub(f) + } + + val xxx = genericAndComplexIdFunction(false) + + nesting() // 100 + } + + def scope3[T](t: T) = { + val x = 0 + + def unused(u: T) = 0 + + def inner() = x + + val y = inner() + + y + } + + def foo(x: Int) = x + + def two[A, B](a: A, b: B) = 0 + def one[C](c: C) = two(c, 0) + def zero() = two(0, 0) + + def oneA[A](a: A) = two(a, 0) + def oneB[B](b: B) = two(0, b) + + def oneABbool() = oneA(true) + oneB(false) + def oneABint() = oneA(42) + oneB(58) + + case class Pair[A, B](a: A, b: B) + + def pairII() = Pair(0, 0) + def pairIB() = Pair(0, true) + def pairBI() = Pair(true, 0) + def pairBB() = Pair(true, true) + + def _main() = { + scope + scope2 + scope3(true) + scope3(42) + + val x = 42 + val y = foo(x) + val z = zero() + val ab = oneABbool() + oneABint() + + val p1 = pairII() + val p2 = pairIB() + val p3 = pairBI() + val p4 = pairBB() + val p5 = pairBB() + val p6 = Pair(true, true) + + val w1 = woo(42) + val w2 = woo(false) + + val o1 = opt(42) + val o2 = opt(false) + + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala b/src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala new file mode 100644 index 000000000..8725a6e20 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala @@ -0,0 +1,22 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object UsingConcreteClasses { + + abstract class Parent + case class Child(opt: Some[Int]) extends Parent + + def _main() = { + val child = Child(Some(42)) + + if (child.opt.v == 42) 0 + else 1 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/valid/Inheritance9.scala b/src/test/resources/regression/genc/valid/Inheritance9.scala new file mode 100644 index 000000000..51c8de207 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Inheritance9.scala @@ -0,0 +1,143 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object Inheritance9 { + + case class M(var value: Int) + + abstract class Base + case class X(flag: Boolean) extends Base + case class Y(m: M) extends Base + + abstract class Base2 + case class V(y: Y) extends Base2 + case class W(v: V) + + case class ArrayWrapper(xs: Array[X]) + + def barX(i: X) = 101 + def barY(g: Y) = 202 + def barM(m: M) = { m.value = m.value + 1; 303 } + def barB(b: Base) = b match { + case x: X => barX(x) + case y: Y => barM(y.m) + barY(y) + } + + def barBfromX(x: X) = barB(x) + + def testCalls(): Int = { + val y = Y(M(42)) + + if (y.m.value == 42) { + + barM(y.m) // y.m.value++ + + if (y.m.value == 43) { + + if (barY(y) == 202 && barB(y) == 505) { // y.m.value++ + + if (y.m.value == 44) { + + val x = X(true) + + if (barX(x) == barB(x)) { + + if (barBfromX(x) == 101) 0 + else 6 + + } else 5 + + } else 4 + + } else 3 + + } else 2 + + } else 1 + } ensuring { _ == 0 } + + def generate() = Y(M(0)) + + def testAssign(): Int = { + var x = X(false) + x = X(true) + + if (x.flag) { + + val array = Array(generate(), generate()) + + array(0).m.value = 1 + + if (array(1).m.value == 0) { + + barB(array(1)) + + if (array(0).m.value == array(1).m.value) { + + val w = W(V(Y(M(1)))) + + w.v.y.m.value = 2 + + barB(X(true)) + barB(w.v.y) + + if (w.v.y.m.value == 3) 0 + else 40 + + } else 30 + + } else 20 + + } else 10 + } ensuring { _ == 0 } + + def testArrayField(): Int = { + val a = ArrayWrapper(Array.fill(3)(X(false))) + + if (barX(a.xs(0)) == barB(a.xs(0))) { + + if (barBfromX(a.xs(1)) == 101) { + + if (a.xs(2).flag == false) 0 + else 300 + + } else 200 + + } else 100 + } ensuring { _ == 0 } + + abstract class SomeBase + case class SomeDerived(x: Int) extends SomeBase + + abstract class SomeOtherBase + case class SomeOtherDerived(d: SomeDerived) extends SomeOtherBase + + def zooo(d: SomeDerived) = d.x + + def testNestedConcreteTypes(): Int = { + val d = SomeDerived(42) + if (d.x == 42) { + + val d2 = SomeOtherDerived(SomeDerived(58)) + + if (d2.d.x == d2.d.x) { + if (-58 == -(d2.d.x)) { + if (58 == d2.d.x) { + + if (zooo(d2.d) == d2.d.x) 0 + else 5 + + } else 4 + } else 3 + } else 2 + + } else 1 + } ensuring { _ == 0 } + + def _main() = testCalls + testAssign + testArrayField + testNestedConcreteTypes + + @extern + def main(args: Array[String]): Unit = _main() +} + From 6e569307a77df11555657d4f304be139685365d9 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 10 Nov 2016 11:47:57 +0100 Subject: [PATCH 08/77] Make sure to use a new reporter for each test in GenCSuite This avoids propagating failures from one test to the next. --- src/test/scala/leon/genc/GenCSuite.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala index 517dbb14d..0d22d8c18 100644 --- a/src/test/scala/leon/genc/GenCSuite.scala +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -46,7 +46,7 @@ class GenCSuite extends LeonRegressionSuite { override def toString = title } - private object WarningOrAboveReporter extends DefaultReporter(Set()) { + private class WarningOrAboveReporter extends DefaultReporter(Set()) { override def emit(msg: Message): Unit = msg.severity match { case this.WARNING | this.FATAL | this.ERROR | this.INTERNAL => super.emit(msg) @@ -55,7 +55,7 @@ class GenCSuite extends LeonRegressionSuite { } override def createLeonContext(opts: String*): LeonContext = { - val reporter = WarningOrAboveReporter + val reporter = new WarningOrAboveReporter Main.processOptions(opts.toList).copy(reporter = reporter, interruptManager = new InterruptManager(reporter)) } From 0e9528c103d324cefc4671d17b69e415a64fbf5d Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 12 Nov 2016 15:31:00 +0100 Subject: [PATCH 09/77] Improve ClassLifter for fields and arrays --- src/main/scala/leon/genc/ir/ClassLifter.scala | 202 +++++++++++++++--- .../scala/leon/genc/phases/LiftingPhase.scala | 4 +- 2 files changed, 176 insertions(+), 30 deletions(-) diff --git a/src/main/scala/leon/genc/ir/ClassLifter.scala b/src/main/scala/leon/genc/ir/ClassLifter.scala index 2c0e8e153..641835c62 100644 --- a/src/main/scala/leon/genc/ir/ClassLifter.scala +++ b/src/main/scala/leon/genc/ir/ClassLifter.scala @@ -6,60 +6,206 @@ package ir import IRs.{ NIR, LIR } -// Lift object type to their top level type in order to properly use tagged union. -// TODO do the same for class fields!!! -// TODO use use env, maybe??? +import collection.mutable.{ Map => MutableMap, Set => MutableSet } + +// Lift class types to their hierarchy top type in order to properly use tagged union. final class ClassLifter(val ctx: LeonContext) extends Transformer(NIR, LIR) with MiniReporter { import from._ - type Mapping = Map[ValDef, Option[to.AsA]] + type Env = Boolean // === lift flag + val Ø = false - class Env(val mapping: Mapping, val liftFlag: Boolean) { - def ++(other: Mapping) = new Env(mapping ++ other, liftFlag) - def ++(other: Env) = new Env(mapping ++ other.mapping, liftFlag || other.liftFlag) + private val lift = true - def dontLift = new Env(mapping, false) - } + // We use a global database to ease the recursion. This works because ValDef's are unique. + private val valDB = MutableMap[ValDef, (to.ValDef, to.ClassType)]() // known runtime class type for values + private val arrayDB = MutableMap[ValDef, (to.ValDef, to.ClassType)]() // idem but for array elements - val Ø: Env = new Env(Map.empty, true) // lift type by default + private val fieldValDB = MutableMap[(to.ClassDef, Id), to.ClassType]() // idem for class field type + private val fieldArrayDB = MutableMap[(to.ClassDef, Id), to.ClassType]() // idem for the elements of class fields that are arrays + + private def isKnownValField(cd: to.ClassDef, fieldId: to.Id): Boolean = fieldValDB contains (cd -> fieldId) + private def isKnownArrayField(cd: to.ClassDef, fieldId: to.Id): Boolean = fieldArrayDB contains (cd -> fieldId) // Lift context, params & return type override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { val id = fd.id - val returnType = rec(fd.returnType) - - val (ctx, ctxAccess) = (fd.ctx map lift).unzip - val (params, paramsAccess) = (fd.params map lift).unzip - - // Build our new environment - val newEnv = env ++ ((fd.ctx ++ fd.params) zip (ctxAccess ++ paramsAccess)).toMap + val returnType = rec(fd.returnType)(lift) + val ctx = fd.ctx map lift + val params = fd.params map lift // Handle recursive functions val newer = to.FunDef(id, returnType, ctx, params, null) registerFunction(fd, newer) - newer.body = rec(fd.body)(newEnv) + newer.body = rec(fd.body)(lift) newer } - override def rec(typ: Type)(implicit env: Env): to.Type = super.rec(typ) match { - case to.ClassType(clazz) if env.liftFlag => to.ClassType(clazz.hierarchyTop) - case typ => typ + // Lift fields types + override def recImpl(cd0: ClassDef, parent: Option[to.ClassDef])(implicit env: Env): to.ClassDef = { + val id = cd0.id + val isAbstract = cd0.isAbstract + + val valFieldsToRegister = MutableSet[(Id, to.ClassType)]() + val arrayFieldsToRegister = MutableSet[(Id, to.ClassType)]() // for the element of the arrays + + val fields = cd0.fields map { vd0 => // This is similar to lift(ValDef) but here we need to defer the registration + val vd = rec(vd0)(lift) + + // "Pre"-register fields if class type or array type was lifted + val typ = rec(vd0.getType)(!lift) + typ match { + case ct @ to.ClassType(c) if c.hierarchyTop != c => + valFieldsToRegister += (vd.id -> ct) + + case to.ArrayType(ct @ to.ClassType(c)) if c.hierarchyTop != c => + arrayFieldsToRegister += (vd.id -> ct) + + case _ => () + } + + vd + } + + val cd = to.ClassDef(id, parent, fields, isAbstract) + + // Actually register the classes/arrays now that we have the corresponding ClassDef + valFieldsToRegister foreach { case (id, ct) => + fieldValDB += (cd, id) -> ct + } + + arrayFieldsToRegister foreach { case (id, ct) => + fieldArrayDB += (cd, id) -> ct + } + + cd + } + + override def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = e match { + case Decl(vd0) => + val vd = lift(vd0) + to.Decl(vd) -> env + + case DeclInit(vd0, value0) => + val vd = lift(vd0) + val value = rec(value0)(lift) + to.DeclInit(vd, value) -> env + + case FieldAccess(Castable(asa), fieldId) => + to.FieldAccess(asa, fieldId) -> env + + case App(fd0, ctx0, args0) => + val fd = rec(fd0) + + // Don't pass a casted object but the object itself + // (this can happen with pattern matching translation). + val ctx = ctx0 map removeTopCast + val args = args0 map removeTopCast + + to.App(fd, ctx, args) -> env + + case _ => super.recImpl(e) + } + + override def rec(typ: Type)(implicit lift: Env): to.Type = typ match { + case ClassType(clazz) if lift => to.ClassType(rec(clazz.hierarchyTop)) + case ArrayType(ArrayType(ClassType(_))) => fatalError("Multidimentional arrays of objects are not yet supported") + case typ => super.rec(typ) } - // Lift only for classes, return (vd, None) for, e.g., primitive or array - private def lift(vd0: ValDef)(implicit env: Env): (to.ValDef, Option[to.AsA]) = { - val vd = rec(vd0) + private def removeTopCast(e: Expr): to.Expr = rec(e)(lift) match { + case to.AsA(expr, _) => expr + case e => e + } - val typ = rec(vd0.getType)(env.dontLift) - val access = typ match { - case ct @ to.ClassType(_) => Some(to.AsA(to.Binding(vd), ct)) + private object Castable { + def unapply(e: Expr): Option[to.Expr] = e match { + case CastableImpl(asa, _) => Some(asa) case _ => None } + } + + private object ClassTypedExpr { + def unapply(e: Expr): Option[(to.Expr, to.ClassDef)] = e.getType match { + case ClassType(cd) => Some(rec(e)(lift) -> rec(cd)(!lift)) + case _ => None + } + } + + // An expression can be safely cast to it known initial type (i.e. before lifting) when: + // - the vd referenced by a binding was registered with its unlifted type; + // - accessing a class field that was lifted, either by recursion or through an expression + // (e.g. function call) of a known conrete class type (before lifting); + // - accessing an element of an array that was lifted through a registered vd or a field + // access. + private object CastableImpl { + def unapply(e0: Expr): Option[(to.AsA, to.ClassDef)] = e0 match { + case Binding(vd0) if valDB contains vd0 => + val (vd, ct) = valDB(vd0) + val asa = to.AsA(to.Binding(vd), ct) + val cd = ct.clazz + + Some(asa -> cd) + + case FieldAccess(CastableImpl(asa1, cd1), fieldId) if isKnownValField(cd1, fieldId) => + val ct2 = fieldValDB(cd1 -> fieldId) + val asa2 = to.AsA(to.FieldAccess(asa1, fieldId), ct2) + val cd2 = ct2.clazz + + Some(asa2 -> cd2) + + case FieldAccess(ClassTypedExpr(e, cd1), fieldId) if isKnownValField(cd1, fieldId) => + val ct2 = fieldValDB(cd1 -> fieldId) + val asa2 = to.AsA(to.FieldAccess(e, fieldId), ct2) + val cd2 = ct2.clazz + + Some(asa2 -> cd2) + + case ArrayAccess(Binding(vd0), index0) if arrayDB contains vd0 => + val (vd, ct) = arrayDB(vd0) + val asa = to.AsA(to.ArrayAccess(to.Binding(vd), rec(index0)(lift)), ct) + val cd = ct.clazz + + Some(asa -> cd) + + case ArrayAccess(FieldAccess(CastableImpl(asa1, cd1), fieldId), index0) if isKnownArrayField(cd1, fieldId) => + val ct2 = fieldArrayDB(cd1 -> fieldId) + val asa2 = to.AsA(to.ArrayAccess(to.FieldAccess(asa1, fieldId), rec(index0)(lift)), ct2) + val cd2 = ct2.clazz + + Some(asa2 -> cd2) + + case ArrayAccess(FieldAccess(ClassTypedExpr(e, cd1), fieldId), index0) if isKnownArrayField(cd1, fieldId) => + val ct2 = fieldArrayDB(cd1 -> fieldId) + val asa2 = to.AsA(to.ArrayAccess(to.FieldAccess(e, fieldId), rec(index0)(lift)), ct2) + val cd2 = ct2.clazz + + Some(asa2 -> cd2) + + case _ => + None + } + } + + private def lift(vd0: ValDef): to.ValDef = { + val vd = rec(vd0)(lift) + val typ = rec(vd0.getType)(!lift) + + // Register val if class type or array type was lifted + typ match { + case ct @ to.ClassType(c) if c.hierarchyTop != c => + valDB += vd0 -> (vd, ct) + + case to.ArrayType(ct @ to.ClassType(c)) if c.hierarchyTop != c => + arrayDB += vd0 -> (vd, ct) + + case _ => () + } - vd -> access + vd } } diff --git a/src/main/scala/leon/genc/phases/LiftingPhase.scala b/src/main/scala/leon/genc/phases/LiftingPhase.scala index 71d3ab2ab..180335b1b 100644 --- a/src/main/scala/leon/genc/phases/LiftingPhase.scala +++ b/src/main/scala/leon/genc/phases/LiftingPhase.scala @@ -23,8 +23,8 @@ private[genc] object LiftingPhase extends TimedLeonPhase[NIR.ProgDef, LIR.ProgDe val reporter = MiniReporter(ctx) import reporter._ - val referencing = new ClassLifter(ctx) - val lprog = referencing(nprog) + val lifter = new ClassLifter(ctx) + val lprog = lifter(nprog) debugTree("RESUTING LIR PROGRAM", lprog) From 09cb1981019cf46c5699821029eaa792f811c2bf Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 12 Nov 2016 15:34:16 +0100 Subject: [PATCH 10/77] Reorder GenC internal pipeline --- src/main/scala/leon/genc/GenerateCPhase.scala | 2 +- src/main/scala/leon/genc/ir/Normaliser.scala | 4 +-- .../scala/leon/genc/ir/Referentiator.scala | 31 ++++++++++--------- .../scala/leon/genc/phases/IR2CPhase.scala | 12 +++---- .../leon/genc/phases/NormalisationPhase.scala | 10 +++--- .../leon/genc/phases/ReferencingPhase.scala | 10 +++--- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/main/scala/leon/genc/GenerateCPhase.scala b/src/main/scala/leon/genc/GenerateCPhase.scala index c1189fdcb..e5cf144ac 100644 --- a/src/main/scala/leon/genc/GenerateCPhase.scala +++ b/src/main/scala/leon/genc/GenerateCPhase.scala @@ -19,9 +19,9 @@ object GenerateCPhase extends LeonPhase[Program, CAST.Prog] { ComputeDependenciesPhase andThen Pipeline.both(NoopPhase[Dependencies], ComputeFunCtxPhase) andThen Scala2IRPhase andThen - ReferencingPhase andThen NormalisationPhase andThen LiftingPhase andThen + ReferencingPhase andThen IR2CPhase def run(ctx: LeonContext, prog: Program) = pipeline.run(ctx, prog) diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala index 336d53ace..d5569a0df 100644 --- a/src/main/scala/leon/genc/ir/Normaliser.scala +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -14,7 +14,7 @@ import IRs._ * condition, ...) and ensure execution order match between Scala & C execution models by * adding explicit execution points. */ -final class Normaliser(val ctx: LeonContext) extends Transformer(RIR, NIR) with MiniReporter with NoEnv { +final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with MiniReporter with NoEnv { import from._ // Inject return in functions that need it @@ -283,6 +283,6 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(RIR, NIR) with private val freshCounter = new utils.UniqueCounter[String]() - private val backward = new Transformer(NIR, RIR) with NoEnv + private val backward = new Transformer(NIR, CIR) with NoEnv } diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 47856751a..733ce9400 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -21,11 +21,11 @@ import IRs._ * Array are mutables, but once converted into C they are wrapped into an immutable struct; * we therefore do not take array by reference because this would only add an indirection */ -final class Referentiator(val ctx: LeonContext) extends Transformer(CIR, RIR) with MiniReporter { +final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) with MiniReporter { import from._ - type Env = Map[CIR.ValDef, RIR.ValDef] - val Ø = Map[CIR.ValDef, RIR.ValDef]() + type Env = Map[ValDef, to.ValDef] + val Ø = Map[ValDef, to.ValDef]() override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { val id = fd.id @@ -71,11 +71,14 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(CIR, RIR) wi case Binding(vd0) => // Check the environment for id; if it's a ref we have to reference it. val vd = env(vd0) - val b = RIR.Binding(vd) + val b = to.Binding(vd) if (vd.isReference) deref(b) -> env else b -> env - case Decl(_) => internalError("Decl is expected only after normalisation/flattening") + case Decl(vd0) => + val vd = rec(vd0) + val newEnv = env + (vd0 -> vd) + to.Decl(vd) -> newEnv case DeclInit(vd0, value0) => val vd = rec(vd0) @@ -103,7 +106,7 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(CIR, RIR) wi } // Adapt the expressions to match w.r.t. references the given parameter types, for argument-like expressions. - private def refMatch(params: Seq[RIR.ValDef])(args: Seq[RIR.Expr]): Seq[RIR.Expr] = { + private def refMatch(params: Seq[to.ValDef])(args: Seq[to.Expr]): Seq[to.Expr] = { (params zip args) map { case (param, arg) => val pr = param.isReference val ar = arg.getType.isReference @@ -117,24 +120,24 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(CIR, RIR) wi } // Build Ref & Deref expression without patterns such as Ref(Deref(_)) - private def ref(e: RIR.Expr, shortLived: Boolean = false): RIR.Expr = e match { - case b @ RIR.Binding(_) => RIR.Ref(b) - case RIR.Deref(e) => e + private def ref(e: to.Expr, shortLived: Boolean = false): to.Expr = e match { + case _: to.Binding | _: to.FieldAccess | _: to.ArrayAccess | _: to.AsA => to.Ref(e) + case to.Deref(e) => e // NOTE Reference can be build on Constructor, but we have to make sure we // don't take the reference of a temporary result for a too long period. - case ctor @ RIR.Construct(_, _) if shortLived => RIR.Ref(ctor) + case ctor @ to.Construct(_, _) if shortLived => to.Ref(ctor) case _ => internalError(s"Making reference on an unsupported expression: $e") } - private def deref(e: RIR.Expr): RIR.Expr = e match { - case b @ RIR.Binding(vd) if vd.isReference => RIR.Deref(b) - case RIR.Ref(e) => e + private def deref(e: to.Expr): to.Expr = e match { + case b @ to.Binding(vd) if vd.isReference => to.Deref(b) + case to.Ref(e) => e case _ => internalError(s"Dereferencing an unsupported expression: $e") } - private def toReference(vd: RIR.ValDef) = vd.copy(typ = RIR.ReferenceType(vd.typ)) + private def toReference(vd: to.ValDef) = vd.copy(typ = to.ReferenceType(vd.typ)) } diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala index 54031f9a3..2c695266c 100644 --- a/src/main/scala/leon/genc/phases/IR2CPhase.scala +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -4,24 +4,24 @@ package leon package genc package phases -import ir.IRs.{ LIR } +import ir.IRs.{ RIR } import ir.PrimitiveTypes._ import ir.Literals._ import ir.Operators._ import genc.{ CAST => C } -import LIR._ +import RIR._ import collection.mutable.{ Map => MutableMap, Set => MutableSet } -private[genc] object IR2CPhase extends TimedLeonPhase[LIR.ProgDef, CAST.Prog] { +private[genc] object IR2CPhase extends TimedLeonPhase[RIR.ProgDef, CAST.Prog] { val name = "CASTer" val description = "Translate the IR tree into the final C AST" - def getTimer(ctx: LeonContext) = ctx.timers.genc.get("LIR -> CAST") + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("RIR -> CAST") - def apply(ctx: LeonContext, ir: LIR.ProgDef): CAST.Prog = new IR2CImpl(ctx)(ir) + def apply(ctx: LeonContext, ir: RIR.ProgDef): CAST.Prog = new IR2CImpl(ctx)(ir) } // This implementation is basically a Transformer that produce something which isn't an IR tree. @@ -61,7 +61,7 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { private def rec(prog: ProgDef): C.Prog = { // Convert all the functions and types of the input program - val finder = new ir.DependencyFinder(LIR) + val finder = new ir.DependencyFinder(RIR) finder(prog) finder.getFunctions foreach rec diff --git a/src/main/scala/leon/genc/phases/NormalisationPhase.scala b/src/main/scala/leon/genc/phases/NormalisationPhase.scala index 68b5cac10..a2a30a490 100644 --- a/src/main/scala/leon/genc/phases/NormalisationPhase.scala +++ b/src/main/scala/leon/genc/phases/NormalisationPhase.scala @@ -4,25 +4,25 @@ package leon package genc package phases -import ir.IRs.{ RIR, NIR } +import ir.IRs.{ CIR, NIR } import ir.{ Normaliser } /* * Normalise the program by adding explicit execution points and making sure * argument-like expressions are "simple" expressions (and not e.g. blocks). */ -private[genc] object NormalisationPhase extends TimedLeonPhase[RIR.ProgDef, NIR.ProgDef] { +private[genc] object NormalisationPhase extends TimedLeonPhase[CIR.ProgDef, NIR.ProgDef] { val name = "Normaliser" val description = "Normalise IR to match the C execution model" - def getTimer(ctx: LeonContext) = ctx.timers.genc.get("RIR -> NIR") + def getTimer(ctx: LeonContext) = ctx.timers.genc.get("CIR -> NIR") - def apply(ctx: LeonContext, rprog: RIR.ProgDef): NIR.ProgDef = { + def apply(ctx: LeonContext, cprog: CIR.ProgDef): NIR.ProgDef = { val reporter = MiniReporter(ctx) import reporter._ val normaliser = new Normaliser(ctx) - val nprog = normaliser(rprog) + val nprog = normaliser(cprog) debugTree("RESUTING NIR PROGRAM", nprog) diff --git a/src/main/scala/leon/genc/phases/ReferencingPhase.scala b/src/main/scala/leon/genc/phases/ReferencingPhase.scala index c6c759c5d..3ef418bff 100644 --- a/src/main/scala/leon/genc/phases/ReferencingPhase.scala +++ b/src/main/scala/leon/genc/phases/ReferencingPhase.scala @@ -4,7 +4,7 @@ package leon package genc package phases -import ir.IRs.{ CIR, RIR } +import ir.IRs.{ LIR, RIR } import ir.{ Referentiator } /* @@ -15,18 +15,18 @@ import ir.{ Referentiator } * * NOTE a ReferenceType(T) is basically a T* in C. */ -private[genc] object ReferencingPhase extends TimedLeonPhase[CIR.ProgDef, RIR.ProgDef] { +private[genc] object ReferencingPhase extends TimedLeonPhase[LIR.ProgDef, RIR.ProgDef] { val name = "Referencer" - val description = "Add 'referencing' to the input CIR program to produce a RIR program" + val description = "Add 'referencing' to the input LIR program to produce a RIR program" def getTimer(ctx: LeonContext) = ctx.timers.genc.get("CIR -> RIR") - def apply(ctx: LeonContext, cprog: CIR.ProgDef): RIR.ProgDef = { + def apply(ctx: LeonContext, lprog: LIR.ProgDef): RIR.ProgDef = { val reporter = MiniReporter(ctx) import reporter._ val referencing = new Referentiator(ctx) - val rprog = referencing(cprog) + val rprog = referencing(lprog) debugTree("RESUTING RIR PROGRAM", rprog) From 6cd74d0150c373500c508e2cb3c63612681ac85f Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 12 Nov 2016 15:35:37 +0100 Subject: [PATCH 11/77] Cleanly reject unsupported operators --- src/main/scala/leon/genc/ir/IR.scala | 11 +++ .../scala/leon/genc/ir/PrimitiveTypes.scala | 12 ++- .../leon/genc/phases/Scala2IRPhase.scala | 93 ++++++++++++------- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index 4f1e4fb08..186a6c034 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -319,6 +319,17 @@ private[genc] sealed trait IR { ir => case ReferenceType(_) => true case _ => false } + + // Category test + def isLogical: Boolean = this match { + case PrimitiveType(pt) => pt.isLogical + case _ => false + } + + def isIntegral: Boolean = this match { + case PrimitiveType(pt) => pt.isIntegral + case _ => false + } } // Type representing Int32, Boolean, ... diff --git a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala index 14a78070a..a9e3a72e6 100644 --- a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala +++ b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala @@ -9,7 +9,17 @@ package ir */ private[genc] object PrimitiveTypes { - sealed abstract class PrimitiveType + sealed abstract class PrimitiveType { + def isLogical: Boolean = this match { + case BoolType => true + case _ => false + } + + def isIntegral: Boolean = this match { + case CharType | Int32Type => true + case _ => false + } + } case object CharType extends PrimitiveType case object Int32Type extends PrimitiveType diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 55ce1c8c0..b6f4a13eb 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -11,6 +11,8 @@ import purescala.{ ExprOps } import purescala.Types._ import xlang.Expressions._ +import utils.Position + import ExtraOps._ import ir.{ PrimitiveTypes => PT, Literals => L, Operators => O } @@ -99,18 +101,47 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe "_" + (tps filterNot { _ == Untyped } map rec map CIR.repId mkString "_") } - private def buildBinOp(lhs: Expr, op: O.BinaryOperator, rhs: Expr)(implicit env: Env) = - CIR.BinOp(op, rec(lhs), rec(rhs)) + // Check validity of the operator + private def checkOp(op: O.Operator, isLogical: Boolean, isIntegral: Boolean, pos: Position): Unit = { + def check(b: Boolean) = if (!b) { + fatalError(s"Invalid use of operator $op with the given operands", pos) + } + + op match { + case _: O.FromLogical with O.FromIntegral => check(isLogical || isIntegral) + case _: O.FromLogical => check(isLogical) + case _: O.FromIntegral => check(isIntegral) + case _ => internalError(s"Unhandled check of operator $op") + } + } + + private def buildBinOp(lhs0: Expr, op: O.BinaryOperator, rhs0: Expr)(pos: Position)(implicit env: Env) = { + val lhs = rec(lhs0) + val rhs = rec(rhs0) - private def buildUnOp(op: O.UnaryOperator, expr: Expr)(implicit env: Env) = - CIR.UnOp(op, rec(expr)) + val logical = lhs.getType.isLogical && rhs.getType.isLogical + val integral = lhs.getType.isIntegral && rhs.getType.isIntegral + checkOp(op, logical, integral, pos) + + CIR.BinOp(op, lhs, rhs) + } + + private def buildUnOp(op: O.UnaryOperator, expr0: Expr)(pos: Position)(implicit env: Env) = { + val expr = rec(expr0) + + val logical = expr.getType.isLogical + val integral = expr.getType.isIntegral + checkOp(op, logical, integral, pos) + + CIR.UnOp(op, expr) + } // Create a binary AST - private def buildMultiOp(op: O.BinaryOperator, exprs: Seq[Expr])(implicit env: Env): CIR.BinOp = exprs.toList match { + private def buildMultiOp(op: O.BinaryOperator, exprs: Seq[Expr])(pos: Position)(implicit env: Env): CIR.BinOp = exprs.toList match { case Nil => internalError("no operands") case a :: Nil => internalError("at least two operands required") - case a :: b :: Nil => CIR.BinOp(op, rec(a), rec(b)) - case a :: xs => CIR.BinOp(op, rec(a), buildMultiOp(op, xs)) + case a :: b :: Nil => buildBinOp(a, op, b)(pos) + case a :: xs => CIR.BinOp(op, rec(a), buildMultiOp(op, xs)(pos)) } // Tuples are converted to classes @@ -259,7 +290,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case LiteralPattern(b, lit) => update(b, scrutinee) - buildBinOp(scrutinee, O.Equals, lit) + buildBinOp(scrutinee, O.Equals, lit)(pat.getPos) case UnapplyPattern(bind, unapply, subs) => fatalError(s"Unapply Pattern, a.k.a. Extractor Objects", pat.getPos) @@ -488,29 +519,29 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case While(cond, body) => CIR.While(rec(cond), rec(body)) - case LessThan(lhs, rhs) => buildBinOp(lhs, O.LessThan, rhs) - case GreaterThan(lhs, rhs) => buildBinOp(lhs, O.GreaterThan, rhs) - case LessEquals(lhs, rhs) => buildBinOp(lhs, O.LessEquals, rhs) - case GreaterEquals(lhs, rhs) => buildBinOp(lhs, O.GreaterEquals, rhs) - case Equals(lhs, rhs) => buildBinOp(lhs, O.Equals, rhs) - case Not(Equals(lhs, rhs)) => buildBinOp(lhs, O.NotEquals, rhs) - - case Not(rhs) => buildUnOp(O.Not, rhs) - case And(exprs) => buildMultiOp(O.And, exprs) - case Or(exprs) => buildMultiOp(O.Or, exprs) - - case BVPlus(lhs, rhs) => buildBinOp(lhs, O.Plus, rhs) - case BVMinus(lhs, rhs) => buildBinOp(lhs, O.Minus, rhs) - case BVUMinus(rhs) => buildUnOp(O.UMinus, rhs) - case BVTimes(lhs, rhs) => buildBinOp(lhs, O.Times, rhs) - case BVDivision(lhs, rhs) => buildBinOp(lhs, O.Div, rhs) - case BVRemainder(lhs, rhs) => buildBinOp(lhs, O.Modulo, rhs) - case BVNot(rhs) => buildUnOp(O.BNot, rhs) - case BVAnd(lhs, rhs) => buildBinOp(lhs, O.BAnd, rhs) - case BVOr(lhs, rhs) => buildBinOp(lhs, O.BOr, rhs) - case BVXOr(lhs, rhs) => buildBinOp(lhs, O.BXor, rhs) - case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, O.BLeftShift, rhs) - case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs) + case LessThan(lhs, rhs) => buildBinOp(lhs, O.LessThan, rhs)(e.getPos) + case GreaterThan(lhs, rhs) => buildBinOp(lhs, O.GreaterThan, rhs)(e.getPos) + case LessEquals(lhs, rhs) => buildBinOp(lhs, O.LessEquals, rhs)(e.getPos) + case GreaterEquals(lhs, rhs) => buildBinOp(lhs, O.GreaterEquals, rhs)(e.getPos) + case Equals(lhs, rhs) => buildBinOp(lhs, O.Equals, rhs)(e.getPos) + case Not(Equals(lhs, rhs)) => buildBinOp(lhs, O.NotEquals, rhs)(e.getPos) + + case Not(rhs) => buildUnOp(O.Not, rhs)(e.getPos) + case And(exprs) => buildMultiOp(O.And, exprs)(e.getPos) + case Or(exprs) => buildMultiOp(O.Or, exprs)(e.getPos) + + case BVPlus(lhs, rhs) => buildBinOp(lhs, O.Plus, rhs)(e.getPos) + case BVMinus(lhs, rhs) => buildBinOp(lhs, O.Minus, rhs)(e.getPos) + case BVUMinus(rhs) => buildUnOp(O.UMinus, rhs)(e.getPos) + case BVTimes(lhs, rhs) => buildBinOp(lhs, O.Times, rhs)(e.getPos) + case BVDivision(lhs, rhs) => buildBinOp(lhs, O.Div, rhs)(e.getPos) + case BVRemainder(lhs, rhs) => buildBinOp(lhs, O.Modulo, rhs)(e.getPos) + case BVNot(rhs) => buildUnOp(O.BNot, rhs)(e.getPos) + case BVAnd(lhs, rhs) => buildBinOp(lhs, O.BAnd, rhs)(e.getPos) + case BVOr(lhs, rhs) => buildBinOp(lhs, O.BOr, rhs)(e.getPos) + case BVXOr(lhs, rhs) => buildBinOp(lhs, O.BXor, rhs)(e.getPos) + case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, O.BLeftShift, rhs)(e.getPos) + case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs)(e.getPos) case BVLShiftRight(lhs, rhs) => fatalError("Operator >>> is not supported", e.getPos) case MatchExpr(scrutinee, cases) => convertPatMap(scrutinee, cases) From 3c00391a43b999bb4c882d3301c660c06a8cf1c2 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 12 Nov 2016 15:36:05 +0100 Subject: [PATCH 12/77] Add missing update of IR --- src/main/scala/leon/genc/ir/IR.scala | 25 ++++++++++++++++----- src/main/scala/leon/genc/ir/IRPrinter.scala | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index 186a6c034..c40a3d078 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -95,11 +95,16 @@ private[genc] sealed trait IR { ir => def getHierarchyLeaves: Set[ClassDef] = getFullHierarchy filter { !_.isAbstract } + def isHierarchyMutable: Boolean = getHierarchyLeaves exists { c => c.fields exists { _.isMutable } } + // Get the type of a given field def getFieldType(fieldId: Id): Type = fields collectFirst { case ValDef(id, typ, _) if id == fieldId => typ } match { case Some(typ) => typ - case None => ??? + case None => sys.error(s"no such field $fieldId in class $id") } + + // Check whether `this` is the same as `other` or one of its subclasses. + def <=(other: ClassDef): Boolean = (this == other) || (other.getDirectChildren exists { this <= _ }) } case class ValDef(id: Id, typ: Type, isVar: Boolean) extends Def { @@ -119,11 +124,13 @@ private[genc] sealed trait IR { ir => val typ: ArrayType } + def check(t: Tree)(valid: Boolean) { if (!valid) sys.error(s"Invalid $t") } + // Allocate an array with a compile-time size case class ArrayAllocStatic(typ: ArrayType, length: Int, values: Seq[Expr]) extends ArrayAlloc { - require( - // The type of the values should exactly match the type of the array elements - (values forall { _.getType == typ.base }) && + check(this)( + // The type of the values should match the type of the array elements + (values forall { _.getType <= typ.base }) && // The number of values should match the array size (length == values.length) && // And empty arrays are forbidden @@ -305,8 +312,8 @@ private[genc] sealed trait IR { ir => def isMutable: Boolean = this match { case PrimitiveType(_) => false - // We do *NOT* answer this question for the whole class hierarchy! - case ClassType(clazz) => clazz.fields exists { _.isMutable } + // We do answer this question for the whole class hierarchy! + case ClassType(clazz) => clazz.isHierarchyMutable case ArrayType(_) => true case ReferenceType(_) => true @@ -320,6 +327,12 @@ private[genc] sealed trait IR { ir => case _ => false } + // Check that `other` is equivalent to `this` or is a super class of `this` + def <=(other: Type): Boolean = (this == other) || ((this, other) match { + case (ClassType(cd1), ClassType(cd2)) => cd1 <= cd2 + case _ => false + }) + // Category test def isLogical: Boolean = this match { case PrimitiveType(pt) => pt.isLogical diff --git a/src/main/scala/leon/genc/ir/IRPrinter.scala b/src/main/scala/leon/genc/ir/IRPrinter.scala index d9bd506e0..0765ec67a 100644 --- a/src/main/scala/leon/genc/ir/IRPrinter.scala +++ b/src/main/scala/leon/genc/ir/IRPrinter.scala @@ -27,6 +27,7 @@ final class IRPrinter[S <: IR](val ir: S) { case t: ValDef => rec(t) case t: Expr => rec(t) case t: Type => rec(t) + case t: ArrayAlloc => rec(t) case _ => ??? } From f40514e58809a9aba50f5d9aba9de76cd487cda4 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sat, 12 Nov 2016 19:06:15 +0100 Subject: [PATCH 13/77] Make an invalid test more relevant --- .../regression/genc/invalid/AbsFun.scala | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/test/resources/regression/genc/invalid/AbsFun.scala b/src/test/resources/regression/genc/invalid/AbsFun.scala index bcf4479f6..a58f93fb5 100644 --- a/src/test/resources/regression/genc/invalid/AbsFun.scala +++ b/src/test/resources/regression/genc/invalid/AbsFun.scala @@ -1,10 +1,11 @@ /* Copyright 2009-2016 EPFL, Lausanne */ +import leon.annotation._ import leon.lang._ object AbsFun { - def isPositive(a : Array[Int], size : Int) : Boolean = { + def isPositive(a: Array[Int], size: Int) : Boolean = { require(a.length >= 0 && size <= a.length) rec(0, a, size) } @@ -53,16 +54,12 @@ object AbsFun { res._2 <= tab.length && isPositive(res._1, res._2)) - def property(t: Array[Int], k: Int): Boolean = { - require(isPositive(t, k) && t.length >= 0 && k >= 0) - if(k < t.length) { - val nt = if(t(k) < 0) { - t.updated(k, -t(k)) - } else { - t.updated(k, t(k)) - } - isPositive(nt, k+1) - } else true - } holds + def _main(): Unit = { + val array = Array(0, 5, 3, 10, -5) + val absArray = abs(array) + } + + @extern + def main(args: Array[String]): Unit = _main() } From 3907f386adcd2345a9dd780ed569891380eb1fff Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sun, 13 Nov 2016 12:50:46 +0100 Subject: [PATCH 14/77] Blind attempt to improve GenCSuite for Larabot Hope is that shutting down fsc will prevent two concurrent build to mess with one another. --- src/test/scala/leon/genc/GenCSuite.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala index 0d22d8c18..2f1965d72 100644 --- a/src/test/scala/leon/genc/GenCSuite.scala +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -289,6 +289,8 @@ class GenCSuite extends LeonRegressionSuite { val scalac = "fsc" val scala = "scala" + info(s"Compiling & evaluating Scala program") + val compile = s"$scalac -deprecation ${libraries.mkString(" ")} $source -d $outClasses" val runBase = s"$scala -cp $outClasses ${xCtx.progName}" @@ -297,9 +299,6 @@ class GenCSuite extends LeonRegressionSuite { case None => Process(runBase) } - // info(s"COMPILE: $compile") - // info(s"RUN: $run") - val outputFile = new File(s"$tmpDir/${xCtx.progName}.scala_output") val command = compile #&& (run #> outputFile) @@ -311,6 +310,12 @@ class GenCSuite extends LeonRegressionSuite { outputFile } + private def resetFsc() { + info(s"Shutting down fsc") + val status = runProcess("fsc -shutdown") + if (status != 0) info("Failed to shut down fsc") + } + // Evaluate both Scala and C programs, making sure their output matches private def evaluate(xCtx: ExtendedContext)(binaries: Seq[String]) = { val cOuts = binaries map { bin => Source.fromFile(evaluateC(xCtx, bin)).getLines } @@ -370,6 +375,8 @@ class GenCSuite extends LeonRegressionSuite { } protected def testAll() = { + resetFsc() + // Set C compiler according to the platform we're currently running on detectCompilers match { case Nil => From 0321eb530317e64b2a7b7fc10ab93c1cedbbc878 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sun, 13 Nov 2016 13:05:22 +0100 Subject: [PATCH 15/77] Explicitly reject ArrayUpdated constructs ArrayUpdated, which represents Array.updated(index, newValue) and therefore builds a new array, should not be confused by ArrayUpdate, which updates a given array element in place. --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index b6f4a13eb..1080aaa44 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -470,6 +470,8 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case ArraySelect(array, index) => CIR.ArrayAccess(rec(array), rec(index)) + case ArrayUpdated(array, index, newValue) => fatalError(s"Unsupported copy of array", e.getPos) + case ArrayUpdate(array, index, value) => CIR.Assign(CIR.ArrayAccess(rec(array), rec(index)), rec(value)) From 7c5fe34123474a9a30b981d90cafb199ecc5231d Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 14 Nov 2016 11:25:52 +0100 Subject: [PATCH 16/77] Improve error messages for unsupported casts --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 1080aaa44..c3abe692c 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -155,6 +155,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case _ => internalError("Unexpected ${typ.getClass} instead of TupleType") } + private def castNotSupported(ct: ClassType): Boolean = + ct.classDef.isAbstract && ct.classDef.hasParent + // When converting expressions, we keep track of the variable in scope to build Bindings type Env = Map[Identifier, CIR.ValDef] @@ -547,7 +550,15 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case BVLShiftRight(lhs, rhs) => fatalError("Operator >>> is not supported", e.getPos) case MatchExpr(scrutinee, cases) => convertPatMap(scrutinee, cases) + + case IsInstanceOf(expr, ct) if castNotSupported(ct) => + fatalError(s"Membership tests on abstract classes are not supported", e.getPos) + case IsInstanceOf(expr, ct) => CIR.IsA(rec(expr), CIR.ClassType(rec(ct))) + + case AsInstanceOf(expr, ct) if castNotSupported(ct) => + fatalError(s"Cast to abstract classes are not supported", e.getPos) + case AsInstanceOf(expr, ct) => CIR.AsA(rec(expr), CIR.ClassType(rec(ct))) case e => internalError(s"expression `$e` of type ${e.getClass} not handled") From ad0266c03526299d7a5ea391bd7dc7de6dbe7b9c Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 14 Nov 2016 11:52:15 +0100 Subject: [PATCH 17/77] Add chaining regression test --- .../regression/genc/valid/Chaining.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/test/resources/regression/genc/valid/Chaining.scala diff --git a/src/test/resources/regression/genc/valid/Chaining.scala b/src/test/resources/regression/genc/valid/Chaining.scala new file mode 100644 index 000000000..2b9a71825 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Chaining.scala @@ -0,0 +1,23 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object Chaining { + + // Make it mutable to force the usage of references. + case class Counter(var c: Int) { + def inc() = Counter(c + 1) + } + + def foo(c: Int): Int = { + val counter = Counter(c) + counter.inc().inc().c + } ensuring { _ == c + 2 } + + def _main() = foo(-2) + + @extern + def main(args: Array[String]) = _main() + +} + From 862775f1ff728ef4581a546c5846031dcf7599ae Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 15 Nov 2016 15:06:55 +0100 Subject: [PATCH 18/77] Update IntegralColor to no longer use VLA --- .../resources/regression/genc/unverified/IntegralColor.scala | 4 +++- .../resources/regression/genc/valid/IntegralColor.disabled | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 src/test/resources/regression/genc/valid/IntegralColor.disabled diff --git a/src/test/resources/regression/genc/unverified/IntegralColor.scala b/src/test/resources/regression/genc/unverified/IntegralColor.scala index 6d25fd056..ef108ee07 100644 --- a/src/test/resources/regression/genc/unverified/IntegralColor.scala +++ b/src/test/resources/regression/genc/unverified/IntegralColor.scala @@ -155,7 +155,9 @@ object IntegralColor { 1, 2, 1, 1, 1, 1)) - val smoothed = Array.fill(gray.length)(0) + // val smoothed = Array.fill(gray.length)(0) // This is a VLA + assert(gray.length == 4) + val smoothed = Array.fill(4)(0) assert(smoothed.length == expected.length) var idx = 0; diff --git a/src/test/resources/regression/genc/valid/IntegralColor.disabled b/src/test/resources/regression/genc/valid/IntegralColor.disabled deleted file mode 100644 index 7f65a6d93..000000000 --- a/src/test/resources/regression/genc/valid/IntegralColor.disabled +++ /dev/null @@ -1 +0,0 @@ -compcert From 121d76559354d816427da3306c9e52625f42a219 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 15 Nov 2016 15:56:13 +0100 Subject: [PATCH 19/77] Ensure GenC tests actually test something Since the introduction of the new dependency finder, many functions/types were no longer examined by GenC. --- .../genc/valid/ExpressionOrder.scala | 18 ++++++++++-------- .../regression/genc/valid/Generics2.scala | 2 +- .../regression/genc/valid/Generics3.scala | 2 +- .../regression/genc/valid/Generics4.scala | 2 +- .../regression/genc/valid/Generics5.scala | 2 +- .../regression/genc/valid/Inheritance1.scala | 5 ++++- .../regression/genc/valid/Inheritance2.scala | 5 ++++- .../regression/genc/valid/Inheritance3.scala | 6 +++++- .../regression/genc/valid/Inheritance6.scala | 2 +- .../genc/{invalid => valid}/LinearSearch.scala | 10 ++++++++++ 10 files changed, 38 insertions(+), 16 deletions(-) rename src/test/resources/regression/genc/{invalid => valid}/LinearSearch.scala (81%) diff --git a/src/test/resources/regression/genc/valid/ExpressionOrder.scala b/src/test/resources/regression/genc/valid/ExpressionOrder.scala index 10a6f171b..db6d5b526 100644 --- a/src/test/resources/regression/genc/valid/ExpressionOrder.scala +++ b/src/test/resources/regression/genc/valid/ExpressionOrder.scala @@ -43,14 +43,16 @@ object ExpressionOrder { } def _main() = { - bool2int(test0(false), 1) + - bool2int(test1(42), 2) + - bool2int(test2(58), 4) + - bool2int(test3(false), 8) + - bool2int(test4(false), 16) + - bool2int(test6, 32) + - bool2int(test7, 64) + - bool2int(test8, 128) + syntaxCheck(0) + + bool2int(test0(false), 1) + + bool2int(test1(42), 2) + + bool2int(test2(58), 4) + + bool2int(test3(false), 8) + + bool2int(test4(false), 16) + + bool2int(test6, 32) + + bool2int(test7, 64) + + bool2int(test8, 128) } ensuring { _ == 0 } def test0(b: Boolean) = { diff --git a/src/test/resources/regression/genc/valid/Generics2.scala b/src/test/resources/regression/genc/valid/Generics2.scala index 46eb10d8d..07909cf9c 100644 --- a/src/test/resources/regression/genc/valid/Generics2.scala +++ b/src/test/resources/regression/genc/valid/Generics2.scala @@ -4,7 +4,7 @@ import leon.annotation.extern import leon.lang._ object Generics2 { - case class Dummy[T](val t: T) + case class Dummy[T](t: T) def _main() = { val d1 = Dummy(true) diff --git a/src/test/resources/regression/genc/valid/Generics3.scala b/src/test/resources/regression/genc/valid/Generics3.scala index 06c545bf7..c2e4ff321 100644 --- a/src/test/resources/regression/genc/valid/Generics3.scala +++ b/src/test/resources/regression/genc/valid/Generics3.scala @@ -4,7 +4,7 @@ import leon.annotation.extern import leon.lang._ object Generics3 { - case class Dummy[T](val t: T) + case class Dummy[T](t: T) def fun[T](t: Option[T]): Option[Dummy[T]] = { if (t.isDefined) Some(Dummy(t.get)) diff --git a/src/test/resources/regression/genc/valid/Generics4.scala b/src/test/resources/regression/genc/valid/Generics4.scala index 91c9bfb19..733d1637f 100644 --- a/src/test/resources/regression/genc/valid/Generics4.scala +++ b/src/test/resources/regression/genc/valid/Generics4.scala @@ -4,7 +4,7 @@ import leon.annotation.extern import leon.lang._ object Generics4 { - case class Dummy[T](val t: T) + case class Dummy[T](t: T) def fun[T](x: Int) = x diff --git a/src/test/resources/regression/genc/valid/Generics5.scala b/src/test/resources/regression/genc/valid/Generics5.scala index fd4a6ae9f..e3a0fe505 100644 --- a/src/test/resources/regression/genc/valid/Generics5.scala +++ b/src/test/resources/regression/genc/valid/Generics5.scala @@ -4,7 +4,7 @@ import leon.annotation.extern import leon.lang._ object Generics5 { - case class Dummy[T](val t: T) + case class Dummy[T](t: T) def fun[T](x: Int) = x diff --git a/src/test/resources/regression/genc/valid/Inheritance1.scala b/src/test/resources/regression/genc/valid/Inheritance1.scala index 7cc79c049..ed9824a92 100644 --- a/src/test/resources/regression/genc/valid/Inheritance1.scala +++ b/src/test/resources/regression/genc/valid/Inheritance1.scala @@ -10,7 +10,10 @@ object Inheritance1 { case class Derived2(x: Int, y: Int) extends Base def _main() = { - 0 + val d1 = Derived1(0) + val d2 = Derived2(1, 2) + if (d1.x + d2.x + d2.y == 3) 0 + else 1 } ensuring { _ == 0 } @extern diff --git a/src/test/resources/regression/genc/valid/Inheritance2.scala b/src/test/resources/regression/genc/valid/Inheritance2.scala index 5300a836f..b95e4302b 100644 --- a/src/test/resources/regression/genc/valid/Inheritance2.scala +++ b/src/test/resources/regression/genc/valid/Inheritance2.scala @@ -10,7 +10,10 @@ object Inheritance2 { abstract class Base def _main() = { - 0 + val d1 = Derived1(0) + val d2 = Derived2(1, 2) + if (d1.x + d2.x + d2.y == 3) 0 + else 1 } ensuring { _ == 0 } @extern diff --git a/src/test/resources/regression/genc/valid/Inheritance3.scala b/src/test/resources/regression/genc/valid/Inheritance3.scala index 4e42fddc7..d4f63bcce 100644 --- a/src/test/resources/regression/genc/valid/Inheritance3.scala +++ b/src/test/resources/regression/genc/valid/Inheritance3.scala @@ -14,7 +14,11 @@ object Inheritance3 { case class Three(x: Int, y: Int, z: Int) extends Derived def _main() = { - 0 + val a = One(1) + val b = Two(2, 2) + val c = Three(3, 3, 3) + if (a.x + b.x + c.x == 6) 0 + else 1 } ensuring { _ == 0 } @extern diff --git a/src/test/resources/regression/genc/valid/Inheritance6.scala b/src/test/resources/regression/genc/valid/Inheritance6.scala index fe6b125a5..b738ee4c3 100644 --- a/src/test/resources/regression/genc/valid/Inheritance6.scala +++ b/src/test/resources/regression/genc/valid/Inheritance6.scala @@ -21,7 +21,7 @@ object Inheritance6 { def baz(unused: Base) = 0 def _main() = { - 0 + baz(foo) + baz(bar) } ensuring { _ == 0 } @extern diff --git a/src/test/resources/regression/genc/invalid/LinearSearch.scala b/src/test/resources/regression/genc/valid/LinearSearch.scala similarity index 81% rename from src/test/resources/regression/genc/invalid/LinearSearch.scala rename to src/test/resources/regression/genc/valid/LinearSearch.scala index 60e5befe7..59be974a5 100644 --- a/src/test/resources/regression/genc/invalid/LinearSearch.scala +++ b/src/test/resources/regression/genc/valid/LinearSearch.scala @@ -1,5 +1,6 @@ /* Copyright 2009-2016 EPFL, Lausanne */ +import leon.annotation._ import leon.lang._ /* The calculus of Computation textbook */ @@ -39,4 +40,13 @@ object LinearSearch { set } + def _main() = { + val array = Array(55, 0, -5, 9, 13, 42, -27, 0) + if (linearSearch(array, 42) && !linearSearch(array, 58)) 0 + else 1 + } + + @extern + def main(args: Array[String]) = _main() + } From 91b4dfff3d47ffece0045db7d69bd2ecb279eec8 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 13:24:25 +0100 Subject: [PATCH 20/77] Improve normalisation with mutable types --- .../scala/leon/genc/ir/Referentiator.scala | 121 +++++++++++++++++- src/main/scala/leon/genc/ir/Visitor.scala | 1 + .../regression/genc/invalid/Aliasing.scala | 42 ++++++ .../regression/genc/valid/Aliasing4.scala | 38 ++++++ 4 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/regression/genc/invalid/Aliasing.scala create mode 100644 src/test/resources/regression/genc/valid/Aliasing4.scala diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 733ce9400..8c39e1fbc 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -9,6 +9,8 @@ import Literals._ import Operators._ import IRs._ +import collection.mutable.{ Stack => MutableStack, Set => MutableSet } + /* * The main idea is to add ReferenceType on functions' parameters when either: * - the parameter is not an array (C array are always passed by reference using a different mechanism) @@ -19,7 +21,23 @@ import IRs._ * take a reference or dereference it before using it. * * Array are mutables, but once converted into C they are wrapped into an immutable struct; - * we therefore do not take array by reference because this would only add an indirection + * we therefore do not take array by reference because this would only add an indirection. + * + * When normalisation kicks in, we might face the following pattern: + * var norm // no value + * if (cond) norm = v1 else norm = v2 + * If v1 & v2 are: + * - both of reference type, then norm also is a reference; + * - both of non-reference type, then norm is not a regerence either; + * - a mix of reference/non-reference types, then we stop due to a C type conflict. + * + * NOTE The last case could be hanlded with something like what follows, but this is too complex for now. + * Also, this can be solved on the user side trivially. + * var norm: T*; + * var normHelper: T; + * if (cond) norm = ptr; else { normHelper = value; norm = &normHelper } + * + * NOTE It is also possible to have more than two assignment points. This is handled below. */ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) with MiniReporter { import from._ @@ -27,6 +45,15 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi type Env = Map[ValDef, to.ValDef] val Ø = Map[ValDef, to.ValDef]() + // Keep track of the block we are traversing right now + private val blocks = MutableStack[Block]() + + // When looking ahead (for declImpl purposes), binding should not get derefenced. + private var lookingAhead = false + + // Registry of ValDef declared using Decl and which are references. + private val knownDeclRef = MutableSet[to.ValDef]() + override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { val id = fd.id @@ -52,9 +79,14 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi // Handle recursive functions val newer = to.FunDef(id, returnType, ctx, params, null) - registerFunction(fd, newer) - newer.body = rec(fd.body)(newEnv) + // Don't traverse nor register the body if we are looking ahead. It would be useless and wrong, resp. + if (lookingAhead) { + newer.body = to.FunBodyAST(to.Block(to.Lit(Literals.UnitLit) :: Nil)) + } else { + registerFunction(fd, newer) + newer.body = rec(fd.body)(newEnv) + } newer } @@ -68,18 +100,34 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi // or Ref(Deref(_)). This is of course not what we want so we fix it // right away using the ref & deref factory functions. final override def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = e match { + case Binding(vd0) if lookingAhead => + to.Binding(env(vd0)) -> env // "Simply" use the env to build the binding, without deferencing anything + case Binding(vd0) => - // Check the environment for id; if it's a ref we have to reference it. + // Check the environment for id; if it's a ref we have to reference it (expect when looking ahead). val vd = env(vd0) val b = to.Binding(vd) if (vd.isReference) deref(b) -> env else b -> env - case Decl(vd0) => + case b @ Block(es0) => + // Keep track of the current block to "read ahead" and handle the special case for normalisation. + blocks.push(b) + val res = super.recImpl(b) + val popped = blocks.pop() + assert(b == popped) + res + + case Decl(vd0) if lookingAhead => val vd = rec(vd0) val newEnv = env + (vd0 -> vd) to.Decl(vd) -> newEnv + case Decl(vd0) => // When not looking ahead already. + val vd = declImpl(vd0) + val newEnv = env + (vd0 -> vd) + to.Decl(vd) -> newEnv + case DeclInit(vd0, value0) => val vd = rec(vd0) val value = rec(value0) @@ -102,6 +150,15 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi to.Construct(cd, args) -> env + case Assign(Binding(vd0), rhs0) if !lookingAhead && knownDeclRef(env(vd0)) => + // Here we handle the special case that derives from declImpl. + val rhs = rec(rhs0) match { + case to.Deref(rhs) => rhs // We do *not* want this deferencing + case e => internalError(s"Unhandled case $e: ${e.getClass}") + } + + to.Assign(to.Binding(env(vd0)), rhs) -> env + case e => super.recImpl(e) } @@ -139,5 +196,59 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi private def toReference(vd: to.ValDef) = vd.copy(typ = to.ReferenceType(vd.typ)) + // Here we handle the three main cases about normalisation described in the preamble + private def declImpl(vd0: ValDef)(implicit env: Env): to.ValDef = { + require(blocks.nonEmpty && !lookingAhead) + lookingAhead = true + + // Find where we are in the current block + val block = blocks.top + val currentAndNexts = block.exprs dropWhile { + case Decl(vd1) if vd1 == vd0 => false + case _ => true + } + + // Look ahead using "normal" recursion + val vd = rec(vd0) // The type might not be correct here, but it doesn't matter as we will "fix" it later. + val newEnv = env + (vd0 -> vd) + val nexts = rec(Block(currentAndNexts.tail))(newEnv) + + // Collect all assignment right hand sides for vd + val assignments = MutableSet[to.Expr]() + val visitor = new Visitor(to) { + override def visit(e: to.Expr): Unit = e match { + case to.Assign(lhs, rhs) if lhs == to.Binding(vd) => assignments += rhs + case _ => + } + } + + visitor(nexts) + + assert(assignments.nonEmpty) + + // Make sure that all types are equals; + // this of course implies that they are either all references or all non references. + val typeInfos = assignments map { _.getType.isReference } + val isRef = typeInfos.head + if (typeInfos exists { _ != isRef }) { + fatalError(s"Cannot apply reference because of normalisation inconsistency on types. " + + s"Inciminated variable: ${vd.id}; Use `--debug=trees` option to learn why it failed.") + } + + lookingAhead = false + + // Build the ValDef with the correct type. + val res = + if (isRef) toReference(vd) + else vd + + // In case we just created a normalisation variable of reference type, we register it + // to avoid illegal `*norm = *ptr`. + if (isRef) + knownDeclRef += res + + res + } + } diff --git a/src/main/scala/leon/genc/ir/Visitor.scala b/src/main/scala/leon/genc/ir/Visitor.scala index f55b5f68e..262cdef4d 100644 --- a/src/main/scala/leon/genc/ir/Visitor.scala +++ b/src/main/scala/leon/genc/ir/Visitor.scala @@ -20,6 +20,7 @@ abstract class Visitor[S <: IR](final val ir: S) { // Entry point for the visit final def apply(prog: ProgDef): Unit = rec(prog) + final def apply(e: Expr): Unit = rec(e) protected def visit(prog: ProgDef): Unit = () protected def visit(fd: FunDef): Unit = () diff --git a/src/test/resources/regression/genc/invalid/Aliasing.scala b/src/test/resources/regression/genc/invalid/Aliasing.scala new file mode 100644 index 000000000..cd4303f1e --- /dev/null +++ b/src/test/resources/regression/genc/invalid/Aliasing.scala @@ -0,0 +1,42 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing { + + case class MutableInteger(var x: Int) + + def selector(first: Boolean, i: MutableInteger, j: MutableInteger) { + /* // This is simple because no normalisation is needed: + * if (first) mutate(i) + * else mutate(j) + */ + + // This is no longer trivial but doable. + // mutate(if (first) i else j) + + // But this is not possible: the type of i and MutableInteger(666) when references + // are involved don't match (MutableInteger* vs MutableInteger). + mutate(if (first) i else MutableInteger(666)) + } + + def mutate(m: MutableInteger) { + m.x = 0 + } + + def _main(): Int = { + val a = MutableInteger(42) + val b = MutableInteger(58) + + selector(first = true, a, b) + + if (a.x == 0) { + if (b.x == 58) 0 + else 2 + } else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Aliasing4.scala b/src/test/resources/regression/genc/valid/Aliasing4.scala new file mode 100644 index 000000000..094da9175 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing4.scala @@ -0,0 +1,38 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing4 { + + case class MutableInteger(var x: Int) + + def selector(first: Boolean, i: MutableInteger, j: MutableInteger) { + /* // This is simple because no normalisation is needed: + * if (first) mutate(i) + * else mutate(j) + */ + + // This is no longer trivial but doable: + mutate(if (first) i else j) + } + + def mutate(m: MutableInteger) { + m.x = 0 + } + + def _main(): Int = { + val a = MutableInteger(42) + val b = MutableInteger(58) + + selector(first = true, a, b) + + if (a.x == 0) { + if (b.x == 58) 0 + else 2 + } else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + From 4d4ad3f6ef78b777ede69cf067f1117821b8763f Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 13:27:18 +0100 Subject: [PATCH 21/77] Minor improvements, make IRPrinter more verbose for debugging purpose --- src/main/scala/leon/genc/ir/IRPrinter.scala | 4 ++-- src/main/scala/leon/genc/ir/Normaliser.scala | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/leon/genc/ir/IRPrinter.scala b/src/main/scala/leon/genc/ir/IRPrinter.scala index 0765ec67a..e9bf04813 100644 --- a/src/main/scala/leon/genc/ir/IRPrinter.scala +++ b/src/main/scala/leon/genc/ir/IRPrinter.scala @@ -33,7 +33,7 @@ final class IRPrinter[S <: IR](val ir: S) { private def rec(prog: ProgDef)(implicit ptx: Context): String = { val deps = new DependencyFinder(ir) - deps(prog.asInstanceOf[deps.ir.ProgDef]) // Weird cast we have to live with... + deps(prog.asInstanceOf[deps.ir.ProgDef]) // Hugly cast we have to live with... val funs = deps.getFunctions map { _.asInstanceOf[FunDef] } map rec val classes = deps.getClasses map { _.asInstanceOf[ClassDef] } map rec @@ -78,7 +78,7 @@ final class IRPrinter[S <: IR](val ir: S) { } private def rec(e: Expr)(implicit ptx: Context): String = (e: @unchecked) match { - case Binding(vd) => vd.id + case Binding(vd) => "[[ " + vd.id + ": " + rec(vd.getType) + " ]]" case Block(exprs) => "{{ " + (exprs map rec mkString ptx.newLine) + " }}" case Decl(vd) => (if (vd.isVar) "var" else "val") + " " + rec(vd) + " // no value" case DeclInit(vd, value) => (if (vd.isVar) "var" else "val") + " " + rec(vd) + " = " + rec(value) diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala index d5569a0df..d5b385119 100644 --- a/src/main/scala/leon/genc/ir/Normaliser.scala +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -234,7 +234,6 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with case ai @ to.ArrayInit(_) => // Attach the ArrayInit to a DeclInit // Go backwards to reuse code from the main recImpl function - debug(s"going backward on $ai") val ai0 = backward(ai) val norm = freshNormVal(ai.getType, isVar = false) val norm0 = backward(norm) From be8719046ba824b2325b063d53922f6cafdc801b Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 14:53:31 +0100 Subject: [PATCH 22/77] Make GenC tests return a value in [0, 255] to avoid false positive On most systems, the return value from a program is truncated to its least significant byte. --- .../regression/genc/unverified/MaxSum.scala | 2 +- .../genc/valid/ExpressionOrder.scala | 32 +++++++++++++------ .../regression/genc/valid/Inheritance9.scala | 15 ++++++++- .../genc/valid/PatternMatching1.scala | 29 +++++++++++++---- .../genc/valid/PatternMatching2.scala | 21 ++++++++++-- .../genc/valid/PatternMatching3.scala | 21 ++++++++++-- .../genc/valid/PatternMatching4.scala | 21 ++++++++++-- .../regression/genc/valid/TupleArray.scala | 2 +- 8 files changed, 115 insertions(+), 28 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/MaxSum.scala b/src/test/resources/regression/genc/unverified/MaxSum.scala index 972f274ac..e3a9f0fc5 100644 --- a/src/test/resources/regression/genc/unverified/MaxSum.scala +++ b/src/test/resources/regression/genc/unverified/MaxSum.scala @@ -67,7 +67,7 @@ object MaxSum { val sum = sm._1 val max = sm._2 if (sum == 1244 && max == 999) 0 - else -1 + else 1 } ensuring { _ == 0 } @extern diff --git a/src/test/resources/regression/genc/valid/ExpressionOrder.scala b/src/test/resources/regression/genc/valid/ExpressionOrder.scala index db6d5b526..7ae9cfa07 100644 --- a/src/test/resources/regression/genc/valid/ExpressionOrder.scala +++ b/src/test/resources/regression/genc/valid/ExpressionOrder.scala @@ -45,14 +45,16 @@ object ExpressionOrder { def _main() = { syntaxCheck(0) - bool2int(test0(false), 1) + - bool2int(test1(42), 2) + - bool2int(test2(58), 4) + - bool2int(test3(false), 8) + - bool2int(test4(false), 16) + - bool2int(test6, 32) + - bool2int(test7, 64) + - bool2int(test8, 128) + printOnFailure( + bool2int(test0(false), 1) + + bool2int(test1(42), 2) + + bool2int(test2(58), 4) + + bool2int(test3(false), 8) + + bool2int(test4(false), 16) + + bool2int(test6, 32) + + bool2int(test7, 64) + + bool2int(test8, 128) + ) } ensuring { _ == 0 } def test0(b: Boolean) = { @@ -157,8 +159,20 @@ object ExpressionOrder { def bool2int(b: Boolean, f: Int) = if (b) 0 else f; + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + @extern def main(args: Array[String]): Unit = _main() } - diff --git a/src/test/resources/regression/genc/valid/Inheritance9.scala b/src/test/resources/regression/genc/valid/Inheritance9.scala index 51c8de207..95cfd856c 100644 --- a/src/test/resources/regression/genc/valid/Inheritance9.scala +++ b/src/test/resources/regression/genc/valid/Inheritance9.scala @@ -135,7 +135,20 @@ object Inheritance9 { } else 1 } ensuring { _ == 0 } - def _main() = testCalls + testAssign + testArrayField + testNestedConcreteTypes + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + + def _main() = printOnFailure(testCalls + testAssign + testArrayField + testNestedConcreteTypes) @extern def main(args: Array[String]): Unit = _main() diff --git a/src/test/resources/regression/genc/valid/PatternMatching1.scala b/src/test/resources/regression/genc/valid/PatternMatching1.scala index 60dcfac30..12c2d0c60 100644 --- a/src/test/resources/regression/genc/valid/PatternMatching1.scala +++ b/src/test/resources/regression/genc/valid/PatternMatching1.scala @@ -41,15 +41,30 @@ object PatternMatching1 { else power2(testCount) } - expect(0, testLiteralSimple(42)) + - expect(1, testLiteralSimple(58)) + - expect(2, testLiteralSimple(100)) + - expect(-1, testLiteralConditional(-10)) + - expect(0, testLiteralConditional(0)) + - expect(1, testLiteralConditional(16)) + - expect(2, testLiteralConditional(3)) + printOnFailure( + expect(0, testLiteralSimple(42)) + + expect(1, testLiteralSimple(58)) + + expect(2, testLiteralSimple(100)) + + expect(-1, testLiteralConditional(-10)) + + expect(0, testLiteralConditional(0)) + + expect(1, testLiteralConditional(16)) + + expect(2, testLiteralConditional(3)) + ) } ensuring { _ == 0 } + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + @extern def main(args: Array[String]): Unit = _main() diff --git a/src/test/resources/regression/genc/valid/PatternMatching2.scala b/src/test/resources/regression/genc/valid/PatternMatching2.scala index 9b7b97094..5e461cea7 100644 --- a/src/test/resources/regression/genc/valid/PatternMatching2.scala +++ b/src/test/resources/regression/genc/valid/PatternMatching2.scala @@ -48,11 +48,26 @@ object PatternMatching2 { else power2(testCount) } - expect(0, testScrutineeSideEffect(999)) + - expect(1, testScrutineeSideEffect(42)) + - expect(0, testScrutineeSideEffect(0)) + printOnFailure( + expect(0, testScrutineeSideEffect(999)) + + expect(1, testScrutineeSideEffect(42)) + + expect(0, testScrutineeSideEffect(0)) + ) } ensuring { _ == 0 } + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + @extern def main(args: Array[String]): Unit = _main() diff --git a/src/test/resources/regression/genc/valid/PatternMatching3.scala b/src/test/resources/regression/genc/valid/PatternMatching3.scala index 0068c79ed..ce10bb4b9 100644 --- a/src/test/resources/regression/genc/valid/PatternMatching3.scala +++ b/src/test/resources/regression/genc/valid/PatternMatching3.scala @@ -36,11 +36,26 @@ object PatternMatching3 { else power2(testCount) } - expect(42, testCaseClass(Some(42))) + - expect(0, testCaseClass(Some(0))) + - expect(999, testCaseClass(None[Int])) + printOnFailure( + expect(42, testCaseClass(Some(42))) + + expect(0, testCaseClass(Some(0))) + + expect(999, testCaseClass(None[Int])) + ) } ensuring { _ == 0 } + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + @extern def main(args: Array[String]): Unit = _main() diff --git a/src/test/resources/regression/genc/valid/PatternMatching4.scala b/src/test/resources/regression/genc/valid/PatternMatching4.scala index e10232abb..1126645e0 100644 --- a/src/test/resources/regression/genc/valid/PatternMatching4.scala +++ b/src/test/resources/regression/genc/valid/PatternMatching4.scala @@ -36,11 +36,26 @@ object PatternMatching4 { else power2(testCount) } - expect(0, testTuple((0, 0))) + - expect(5, testTuple((0, -5))) + - expect(-5, testTuple((1, -5))) + printOnFailure( + expect(0, testTuple((0, 0))) + + expect(5, testTuple((0, -5))) + + expect(-5, testTuple((1, -5))) + ) } ensuring { _ == 0 } + // Because on Unix, exit code should be in [0, 255], we print the exit code on failure + // and return 1. On success, we do nothing special. + def printOnFailure(exitCode: Int): Int = { + if (exitCode == 0) 0 + else { + implicit val state = leon.io.newState + leon.io.StdOut.print("Error code: ") + leon.io.StdOut.print(exitCode) + leon.io.StdOut.println() + 1 + } + } + @extern def main(args: Array[String]): Unit = _main() diff --git a/src/test/resources/regression/genc/valid/TupleArray.scala b/src/test/resources/regression/genc/valid/TupleArray.scala index e09016055..4b9bf6971 100644 --- a/src/test/resources/regression/genc/valid/TupleArray.scala +++ b/src/test/resources/regression/genc/valid/TupleArray.scala @@ -21,7 +21,7 @@ object TupleArray { val e1 = exists((a, 0)) val e2 = exists((a, -1)) if (e1 && !e2) 0 - else -1 + else 1 } ensuring { _ == 0 } @extern From 240326bce285f30f856cf8d67937940d9e3c97de Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 15:29:10 +0100 Subject: [PATCH 23/77] Add regression test for operator Equals Note: this could get supported by generating the proper function definitions and replacing the operator by a function call. --- .../genc/invalid/OperatorEquals.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/resources/regression/genc/invalid/OperatorEquals.scala diff --git a/src/test/resources/regression/genc/invalid/OperatorEquals.scala b/src/test/resources/regression/genc/invalid/OperatorEquals.scala new file mode 100644 index 000000000..7fccc772b --- /dev/null +++ b/src/test/resources/regression/genc/invalid/OperatorEquals.scala @@ -0,0 +1,22 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object OperatorEquals { + + case class T(x: Int) + + def _main(): Int = { + val x = T(42) + val y = T(42) + // Custom operator `==` not supported + if (x == y) 0 + else 1 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + From ffaa95c54d0c5599b3872f75b6760d54d0d413cb Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 15:29:36 +0100 Subject: [PATCH 24/77] Add more vicious normalisation tests --- .../genc/invalid/UglyArrayUpdate.scala | 22 ++++++++++++ .../genc/valid/ExpressionOrder.scala | 34 ++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/regression/genc/invalid/UglyArrayUpdate.scala diff --git a/src/test/resources/regression/genc/invalid/UglyArrayUpdate.scala b/src/test/resources/regression/genc/invalid/UglyArrayUpdate.scala new file mode 100644 index 000000000..c069d4b24 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/UglyArrayUpdate.scala @@ -0,0 +1,22 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern +import leon.lang._ + +object UglyArrayUpdate { + + def _main() = { + val a = Array(0, 1, 2, 3) + val b = Array(9, 9, 9) + var c = 666 + val i = 9 + (if (i != 9) b else { c = 0; a })(if (i == 9) { c = c + 1; 0 } else 1) = { c = c * 2; c } + + if (a(0) == 2) 0 + else 1 + } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/ExpressionOrder.scala b/src/test/resources/regression/genc/valid/ExpressionOrder.scala index 7ae9cfa07..c8b58b3a4 100644 --- a/src/test/resources/regression/genc/valid/ExpressionOrder.scala +++ b/src/test/resources/regression/genc/valid/ExpressionOrder.scala @@ -53,7 +53,10 @@ object ExpressionOrder { bool2int(test4(false), 16) + bool2int(test6, 32) + bool2int(test7, 64) + - bool2int(test8, 128) + bool2int(test8, 128)+ + bool2int(test9, 256)+ + bool2int(test10, 512)+ + bool2int(test11, 1024) ) } ensuring { _ == 0 } @@ -157,6 +160,35 @@ object ExpressionOrder { bar(2) == 0 }.holds + def test9() = { + var c = 0 + val r = { c = c + 3; c } + { c = c + 1; c } * { c = c * 2; c } + r == 35 && c == 8 + }.holds + + def test10() = { + var c = 0 + + def myfma(x: Int, y: Int, z: Int) = { + require(z == 8 && c == 8) + x + y * z + } + + val r = myfma({ c = c + 3; c }, { c = c + 1; c }, { c = c * 2; c }) + + r == 35 && c == 8 + }.holds + + def test11() = { + val a = Array(0, 1, 2, 3) + val b = Array(9, 9, 9) + var c = 666 + val i = 9 + val x = (if (i != 9) b else { c = 0; a })(if (i == 9) { c = c + 1; 0 } else 1) + + x == 0 && c == 1 + }.holds + def bool2int(b: Boolean, f: Int) = if (b) 0 else f; // Because on Unix, exit code should be in [0, 255], we print the exit code on failure From bd812422535b2b2ab0b636657d86a37df8875ba1 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 16 Nov 2016 18:54:25 +0100 Subject: [PATCH 25/77] Make normaliser great again! --- src/main/scala/leon/genc/ir/Normaliser.scala | 113 ++++++++++++++---- .../scala/leon/genc/ir/Referentiator.scala | 6 + 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala index d5b385119..d064d0829 100644 --- a/src/main/scala/leon/genc/ir/Normaliser.scala +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -40,17 +40,21 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with val (preAlloc, alloc) = alloc0 match { case ArrayAllocStatic(typ, length, values0) => - val (preValues, values) = flattens(values0) + val (preValues, values) = flattenArgs(values0) val alloc = to.ArrayAllocStatic(recAT(typ), length, values) preValues -> alloc case ArrayAllocVLA(typ, length0, valueInit0) => - val (preLength, length) = flatten(length0) - val (preValueInit, valueInit) = flatten(valueInit0) + // Here it's fine to do two independent normalisations because there will be a + // sequence point between the length and the value in the C code anyway. + val (preLength, length) = flatten(length0, allowTopLevelApp = true) + val (preValueInit, valueInit) = flatten(valueInit0, allowTopLevelApp = true) - if (preValueInit.nonEmpty) + if (preValueInit.nonEmpty) { + debug(s"VLA Elements init not supported: ${preValueInit mkString " ~ "} ~ $valueInit") fatalError(s"VLA elements cannot be initialised with a complex expression") + } val alloc = to.ArrayAllocVLA(recAT(typ), length, valueInit) @@ -63,7 +67,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with case DeclInit(vd0, value0) => val vd = rec(vd0) - val (pre, value) = flatten(value0) + val (pre, value) = flatten(value0, allowTopLevelApp = true) val declinit = to.DeclInit(vd, value) @@ -72,14 +76,14 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with case App(fd0, extra0, args0) => val fd = rec(fd0) val extra = extra0 map rec // context argument are trivial enough to not require special handling - val (preArgs, args) = flattens(args0) + val (preArgs, args) = flattenArgs(args0) val app = to.App(fd, extra, args) combine(preArgs :+ app) -> env case Construct(cd0, args0) => val cd = rec(cd0) - val (preArgs, args) = flattens(args0) + val (preArgs, args) = flattenArgs(args0) val ctor = to.Construct(cd, args) combine(preArgs :+ ctor) -> env @@ -93,8 +97,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with combine(preObjekt :+ access) -> env case ArrayAccess(array0, index0) => - val (preArray, array) = flatten(array0) - val (preIndex, index) = flatten(index0) + val (Seq(preArray, preIndex), Seq(array, index)) = flattenAll(array0, index0) val access = to.ArrayAccess(array, index) combine(preArray ++ preIndex :+ access) -> env @@ -105,16 +108,34 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with combine(preArray :+ length) -> env + case Assign(ArrayAccess(array0, index0), rhs0) => + // Add sequence point for index and rhs, but we assume array is simple enough to not require normalisation. + val (preArray, array) = flatten(array0) + val (Seq(preIndex, preRhs), Seq(index, rhs)) = flattenAll(index0, rhs0) + + if (preArray.nonEmpty) + fatalError(s"Unsupported form of array update: $e") + + val assign = to.Assign(to.ArrayAccess(array, index), rhs) + + combine(preIndex ++ preRhs :+ assign) -> env + case Assign(lhs0, rhs0) => val (preLhs, lhs) = flatten(lhs0) + + if (preLhs.nonEmpty) { + debug(s"When processing:\n$e") + internalError(s"Assumed to be invalid Scala code is apparently present in the AST") + } + val (preRhs, rhs) = flatten(rhs0) + val assign = to.Assign(lhs, rhs) - combine(preLhs ++ preRhs :+ assign) -> env + combine(preRhs :+ assign) -> env case BinOp(op, lhs0, rhs0) => - val (preLhs, lhs) = flatten(lhs0) - val (preRhs, rhs) = flatten(rhs0) + val (Seq(preLhs, preRhs), Seq(lhs, rhs)) = flattenAll(lhs0, rhs0) def default = { val binop = to.BinOp(op, lhs, rhs) @@ -141,7 +162,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with combine(pre :+ unop) -> env case If(cond0, thenn0) => - val (preCond, cond) = flatten(cond0) + val (preCond, cond) = flatten(cond0, allowTopLevelApp = true) val thenn = rec(thenn0) val fi = to.If(cond, thenn) @@ -149,7 +170,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with combine(preCond :+ fi) -> env case IfElse(cond0, thenn0, elze0) => - val (preCond, cond) = flatten(cond0) + val (preCond, cond) = flatten(cond0, allowTopLevelApp = true) val thenn = rec(thenn0) val elze = rec(elze0) @@ -158,7 +179,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with combine(preCond :+ fi) -> env case While(cond0, body0) => - val (preCond, cond) = flatten(cond0) + val (preCond, cond) = flatten(cond0, allowTopLevelApp = true) val body = rec(body0) val loop = preCond match { @@ -200,21 +221,47 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with case es => to.buildBlock(es) } - private def flatten(e: Expr)(implicit env: Env): (Seq[to.Expr], to.Expr) = rec(e) match { - case to.Block(init :+ last) => normalise(init, last) - case e => normalise(Seq.empty, e) + // In most cases, we should add an explicit sequence point when calling a function (i.e. by introducing + // a variable holding the result of the function call). However, in a few cases this is not required; + // e.g. when declaring a variable we can directly call a function without needing a (duplicate) sequence + // point. Caller can therefore carefully set `allowTopLevelApp` to true in those cases. + private def flatten(e: Expr, allowTopLevelApp: Boolean = false)(implicit env: Env): (Seq[to.Expr], to.Expr) = { + val (init, last) = rec(e) match { + case to.Block(init :+ last) => (init, last) + case e => (Seq.empty, e) + } + + normalise(init, last, allowTopLevelApp) } - // Extract all "init" together - private def flattens(es: Seq[Expr])(implicit env: Env): (Seq[to.Expr], Seq[to.Expr]) = { - val empty = (Seq[to.Expr](), Seq[to.Expr]()) // initS + lastS - (empty /: es) { case ((inits, lasts), e) => + // Flatten all the given arguments, adding strict normalisation is needed and returning two lists of: + // - the init statements for each argument + // - the arguments themselves + private def flattenAll(args0: Expr*)(implicit env: Env): (Seq[Seq[to.Expr]], Seq[to.Expr]) = { + val empty = (Seq[Seq[to.Expr]](), Seq[to.Expr]()) // initSS + lastS + val (initss1, args1) = (empty /: args0) { case ((initss, lasts), e) => val (init, last) = flatten(e) - (inits ++ init, lasts :+ last) + (initss :+ init, lasts :+ last) + } + + val initssArgs = for (i <- 0 until args1.length) yield { + val (argDeclOpt, arg) = strictNormalisation(args1(i), initss1:_*) + val init = initss1(i) ++ argDeclOpt + (init, arg) } + + initssArgs.unzip + } + + // Extract all "init" together; first regular flatten then a strict normalisation. + private def flattenArgs(args0: Seq[Expr])(implicit env: Env): (Seq[to.Expr], Seq[to.Expr]) = { + val (initss, args) = flattenAll(args0:_*) + val allInit = initss.flatten + + (allInit, args) } - private def normalise(pre: Seq[to.Expr], value: to.Expr): (Seq[to.Expr], to.Expr) = value match { + private def normalise(pre: Seq[to.Expr], value: to.Expr, allowTopLevelApp: Boolean): (Seq[to.Expr], to.Expr) = value match { case fi0 @ to.IfElse(_, _, _) => val norm = freshNormVal(fi0.getType, isVar = true) val decl = to.Decl(norm) @@ -223,7 +270,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with (pre :+ decl :+ fi, binding) - case app @ to.App(fd, _, _) => + case app @ to.App(fd, _, _) if !allowTopLevelApp => // Add explicit execution point by saving the result in a temporary variable val norm = freshNormVal(fd.returnType, isVar = false) val declinit = to.DeclInit(norm, app) @@ -249,6 +296,22 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with case value => (pre, value) } + // Strict normalisation: create a normalisation variable to save the result of an argument if it could be modified + // by an init segment (from any argument) extracted during regular normalisation. + private def strictNormalisation(value: to.Expr, inits: Seq[to.Expr]*): (Option[to.DeclInit], to.Expr) = { + if (inits exists { _.nonEmpty }) { + // We need to store the result in a temporary variable. + val norm = freshNormVal(value.getType, isVar = false) + val binding = to.Binding(norm) + val declinit = to.DeclInit(norm, value) + + (Some(declinit), binding) + } else { + // No init segment, so we can safely use the given value as is. + (None, value) + } + } + // Apply `action` on the AST leaf expressions. private def inject(action: to.Expr => to.Expr)(e: to.Expr): to.Expr = { val injecter = new Transformer(to, to) with NoEnv { injecter => diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 8c39e1fbc..2acc42e08 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -128,6 +128,12 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi val newEnv = env + (vd0 -> vd) to.Decl(vd) -> newEnv + case DeclInit(vd0, Binding(rhs0)) if !lookingAhead && knownDeclRef(env(rhs0)) => + // Here we patch aliases on references generated by normalisation. + val vd = toReference(rec(vd0)) + val newEnv = env + (vd0 -> vd) + to.DeclInit(vd, to.Binding(env(rhs0))) -> newEnv + case DeclInit(vd0, value0) => val vd = rec(vd0) val value = rec(value0) From fe3bb1f9840f27008e0fca6995382b14dab5651b Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 17 Nov 2016 14:22:51 +0100 Subject: [PATCH 26/77] Improve support for nesting & generic functions --- .../leon/genc/phases/Scala2IRPhase.scala | 135 +++++++++++------- .../scala/leon/purescala/Definitions.scala | 2 +- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index c3abe692c..2ee4e3129 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -7,7 +7,7 @@ package phases import purescala.Common._ import purescala.Definitions._ import purescala.Expressions._ -import purescala.{ ExprOps } +import purescala.{ ExprOps, TypeOps } import purescala.Types._ import xlang.Expressions._ @@ -26,7 +26,7 @@ import scala.collection.mutable.{ Map => MutableMap } */ private[genc] object Scala2IRPhase extends TimedLeonPhase[(Dependencies, FunCtxDB), CIR.ProgDef] { val name = "Scala to IR converter" - val description = "Convert the Scala AST into GenC's 'generic' IR" + val description = "Convert the Scala AST into GenC's IR" def getTimer(ctx: LeonContext) = ctx.timers.genc.get("Scala -> CIR") @@ -61,14 +61,19 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe /**************************************************************************************************** * Entry point of conversion * ****************************************************************************************************/ - def apply(entryPoint: FunDef): CIR.FunDef = rec(entryPoint.typed) + def apply(entryPoint: FunDef): CIR.FunDef = rec(entryPoint.typed)(Map.empty) /**************************************************************************************************** * Caches * ****************************************************************************************************/ - private val funCache = MutableMap[TypedFunDef, CIR.FunDef]() + + // For functions, we associate each TypedFunDef to a CIR.FunDef for each "type context" (TypeMapping). + // This is very important for (non-generic) functions nested in a generic function because for N + // instantiation of the outer function we have only one TypedFunDef for the inner function. + private val funCache = MutableMap[(TypedFunDef, TypeMapping), CIR.FunDef]() + private val classCache = MutableMap[ClassType, CIR.ClassDef]() @@ -76,31 +81,50 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe /**************************************************************************************************** * Helper functions * ****************************************************************************************************/ - private def convertVarInfoToArg(vi: VarInfo) = CIR.ValDef(rec(vi.id), rec(vi.typ), vi.isVar) - private def convertVarInfoToParam(vi: VarInfo) = CIR.Binding(convertVarInfoToArg(vi)) + // When converting expressions, we keep track of the variable in scope to build Bindings + // Also, the identifier might not have the correct type (e.g. when generic). We therefore + // need an external *concrete* type. + private type Env = Map[(Identifier, TypeTree), CIR.ValDef] + + // Keep track of generic to concrete type mapping + private type TypeMapping = Map[TypeParameter, TypeTree] + + private def instantiate(typ: TypeTree, tm: TypeMapping): TypeTree = + TypeOps.instantiateType(typ, tm) + + // Here, when context are translated, there might remain some generic types. We use `tm` to make them disapear. + private def convertVarInfoToArg(vi: VarInfo)(implicit tm: TypeMapping) = CIR.ValDef(rec(vi.id), rec(vi.typ), vi.isVar) + private def convertVarInfoToParam(vi: VarInfo)(implicit tm: TypeMapping) = CIR.Binding(convertVarInfoToArg(vi)) // Extract the ValDef from the known one - private def buildBinding(id: Identifier)(implicit env: Env) = CIR.Binding(env(id)) + private def buildBinding(id: Identifier)(implicit env: Env, tm: TypeMapping) = + CIR.Binding(env(id -> instantiate(id.getType, tm))) - private def buildLet(x: Identifier, e: Expr, body: Expr, isVar: Boolean)(implicit env: Env): CIR.Expr = { + private def buildLet(x: Identifier, e: Expr, body: Expr, isVar: Boolean) + (implicit env: Env, tm: TypeMapping): CIR.Expr = { val vd = CIR.ValDef(rec(x), rec(x.getType), isVar) val decl = CIR.DeclInit(vd, rec(e)) - val newEnv = env + (x -> vd) - val rest = rec(body)(newEnv) + val newEnv = env + ((x, instantiate(x.getType, tm)) -> vd) + val rest = rec(body)(newEnv, tm) CIR.buildBlock(Seq(decl, rest)) } - private def buildId(tfd: TypedFunDef): CIR.Id = - rec(tfd.fd.id) + buildIdPostfix(tfd.tps) + // Include the "nesting path" in case of generic functions to avoid ambiguity + private def buildId(tfd: TypedFunDef)(implicit tm: TypeMapping): CIR.Id = + rec(tfd.fd.id) + (if (tfd.tps.nonEmpty) buildIdPostfix(tfd.tps) else buildIdFromTypeMapping(tm)) - private def buildId(ct: ClassType): CIR.Id = + private def buildId(ct: ClassType)(implicit tm: TypeMapping): CIR.Id = rec(ct.classDef.id) + buildIdPostfix(ct.tps) - private def buildIdPostfix(tps: Seq[TypeTree]): CIR.Id = if (tps.isEmpty) "" else { + private def buildIdPostfix(tps: Seq[TypeTree])(implicit tm: TypeMapping): CIR.Id = if (tps.isEmpty) "" else { "_" + (tps filterNot { _ == Untyped } map rec map CIR.repId mkString "_") } + private def buildIdFromTypeMapping(tm: TypeMapping): CIR.Id = if (tm.isEmpty) "" else { + "_" + (tm.values map { t => CIR.repId(rec(t)(tm)) } mkString "_") + } + // Check validity of the operator private def checkOp(op: O.Operator, isLogical: Boolean, isIntegral: Boolean, pos: Position): Unit = { def check(b: Boolean) = if (!b) { @@ -115,7 +139,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } } - private def buildBinOp(lhs0: Expr, op: O.BinaryOperator, rhs0: Expr)(pos: Position)(implicit env: Env) = { + private def buildBinOp(lhs0: Expr, op: O.BinaryOperator, rhs0: Expr) + (pos: Position) + (implicit env: Env, tm: TypeMapping) = { val lhs = rec(lhs0) val rhs = rec(rhs0) @@ -126,7 +152,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe CIR.BinOp(op, lhs, rhs) } - private def buildUnOp(op: O.UnaryOperator, expr0: Expr)(pos: Position)(implicit env: Env) = { + private def buildUnOp(op: O.UnaryOperator, expr0: Expr) + (pos: Position) + (implicit env: Env, tm: TypeMapping) = { val expr = rec(expr0) val logical = expr.getType.isLogical @@ -137,7 +165,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } // Create a binary AST - private def buildMultiOp(op: O.BinaryOperator, exprs: Seq[Expr])(pos: Position)(implicit env: Env): CIR.BinOp = exprs.toList match { + private def buildMultiOp(op: O.BinaryOperator, exprs: Seq[Expr]) + (pos: Position) + (implicit env: Env, tm: TypeMapping): CIR.BinOp = exprs.toList match { case Nil => internalError("no operands") case a :: Nil => internalError("at least two operands required") case a :: b :: Nil => buildBinOp(a, op, b)(pos) @@ -145,7 +175,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } // Tuples are converted to classes - private def tuple2Class(typ: TypeTree): CIR.ClassDef = typ match { + private def tuple2Class(typ: TypeTree)(implicit tm: TypeMapping): CIR.ClassDef = typ match { case TupleType(bases) => val types = bases map rec val fields = types.zipWithIndex map { case (typ, i) => CIR.ValDef("_" + (i+1), typ, isVar = false) } @@ -158,9 +188,6 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe private def castNotSupported(ct: ClassType): Boolean = ct.classDef.isAbstract && ct.classDef.hasParent - // When converting expressions, we keep track of the variable in scope to build Bindings - type Env = Map[Identifier, CIR.ValDef] - /**************************************************************************************************** @@ -184,7 +211,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } } - private def convertPatMap(scrutinee0: Expr, cases0: Seq[MatchCase])(implicit env: Env): CIR.Expr = { + private def convertPatMap(scrutinee0: Expr, cases0: Seq[MatchCase])(implicit env: Env, tm: TypeMapping): CIR.Expr = { require(cases0.nonEmpty) def withTmp(typ: TypeTree, value: Expr, env: Env): (Variable, Some[CIR.DeclInit], Env) = { @@ -192,9 +219,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe val tmpId = rec(tmp0) val tmpTyp = rec(typ) val tmp = CIR.ValDef(tmpId, tmpTyp, isVar = false) - val pre = CIR.DeclInit(tmp, rec(value)(env)) + val pre = CIR.DeclInit(tmp, rec(value)(env, tm)) - (tmp0.toVariable, Some(pre), env + (tmp0 -> tmp)) + (tmp0.toVariable, Some(pre), env + ((tmp0, instantiate(typ, tm)) -> tmp)) } val (scrutinee, preOpt, newEnv) = scrutinee0 match { @@ -208,7 +235,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case e => internalError(s"scrutinee = $e of type ${e.getClass} is not supported") } - val cases = cases0 map { caze => convertCase(scrutinee, caze)(newEnv) } + val cases = cases0 map { caze => convertCase(scrutinee, caze)(newEnv, tm) } // Identify the last case val last = cases.last match { @@ -238,7 +265,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } // Extract the condition, guard and body (rhs) of a match case - private def convertCase(initialScrutinee: Expr, caze: MatchCase)(implicit env: Env): PMCase = { + private def convertCase(initialScrutinee: Expr, caze: MatchCase)(implicit env: Env, tm: TypeMapping): PMCase = { // We need to keep track of binder (and binders in sub-patterns) and their appropriate // substitution. We do so in an Imperative manner with variables -- sorry FP, but it's // much simpler that way! However, to encapsulate things a bit, we use the `update` @@ -313,30 +340,32 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe if (id.name == "_main") "_main" else { val uniqueId = id.uniqueNameDelimited("_") - if (uniqueId endsWith "_1") id.name // when possible, keep the id as clean as possible + if (uniqueId endsWith "_0") id.name // when possible, keep the id as clean as possible else uniqueId } - } // Try first to fetch the function from cache to handle recursive funcitons. - private def rec(tfd: TypedFunDef): CIR.FunDef = funCache get tfd getOrElse { + private def rec(tfd: TypedFunDef)(implicit tm0: TypeMapping): CIR.FunDef = funCache get tfd -> tm0 getOrElse { val id = buildId(tfd) + // We have to manually specify tm1 from now on to avoid using tm0. We mark tm1 as + // implicit as well to generate ambiguity at compile time to avoid forgetting a call site. + implicit val tm1 = tm0 ++ tfd.typesMap + // Make sure to get the id from the function definition, not the typed one, as they don't always match. - val paramTypes = tfd.params map { p => rec(p.getType) } - val paramIds = tfd/*.fd*/.params map { p => rec(p.id) } + val paramTypes = tfd.fd.params map { p => rec(p.getType)(tm1) } + val paramIds = tfd.fd.params map { p => rec(p.id) } val params = (paramIds zip paramTypes) map { case (id, typ) => CIR.ValDef(id, typ, isVar = false) } - // Extract the context for the function definition. - val ctx = ctxDB(tfd.fd) map convertVarInfoToArg - // TODO THERE MIGHT BE SOME GENERICS IN THE CONTEXT!!! + // Extract the context for the function definition, taking care of the remaining generic types in the context. + val ctx = ctxDB(tfd.fd) map { c => convertVarInfoToArg(c)(tm1) } - val returnType = rec(tfd.returnType) + val returnType = rec(tfd.returnType)(tm1) // Build a partial function without body in order to support recursive functions val fun = CIR.FunDef(id, returnType, ctx, params, null) - funCache.update(tfd, fun) + funCache.update(tfd -> tm0, fun) // Register with the callee TypeMapping, *not* the newer // Now proceed with the body val body: CIR.FunBody = @@ -344,14 +373,17 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe val impl = tfd.fd.getManualDefinition CIR.FunBodyManual(impl.includes, impl.code) } else { - val ctxEnv = (ctxDB(tfd.fd) map { _.id }) zip ctx - val paramEnv = (tfd/*.fd*/.params map { _.id }) zip params - val env = (ctxEnv ++ paramEnv).toMap + // Build the new environment from context and parameters + val ctxKeys = ctxDB(tfd.fd) map { c => c.id -> instantiate(c.typ, tm1) } + val ctxEnv = ctxKeys zip ctx - // TODO Find out if this is an issue or not by writing a regression test. - warning(s"we are invalidating the ctx names because we are using the translated version of the body of $id") + val paramKeys = tfd.fd.params map { p => p.id -> instantiate(p.getType, tm1) } + val paramEnv = paramKeys zip params - CIR.FunBodyAST(rec(tfd.fullBody)(env)) + val env = (ctxEnv ++ paramEnv).toMap + + // Recurse on the FunDef body, and not the TypedFunDef one, in order to keep the correct identifiers. + CIR.FunBodyAST(rec(tfd.fd.fullBody)(env, tm1)) } // Now that we have a body, we can fully build the FunDef @@ -360,15 +392,14 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe fun } - private def rec(typ: TypeTree): CIR.Type = typ match { + // We need a type mapping only when converting context argument to remove the remaining generics. + private def rec(typ: TypeTree)(implicit tm: TypeMapping): CIR.Type = typ match { case UnitType => CIR.PrimitiveType(PT.UnitType) case BooleanType => CIR.PrimitiveType(PT.BoolType) case Int32Type => CIR.PrimitiveType(PT.Int32Type) case CharType => CIR.PrimitiveType(PT.CharType) case StringType => CIR.PrimitiveType(PT.StringType) - // case tp @ TypeParameter(_) => CIR.ParametricType(convertId(tp.id)) - // For both case classes and abstract classes: case ct: ClassType => val cd = ct.classDef @@ -387,10 +418,12 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case FunctionType(from, to) => internalError(s"what shoud I do with $from -> $to") + case tp: TypeParameter => rec(instantiate(tp, tm)) + case t => internalError(s"type tree of type ${t.getClass} not handled") } - private def rec(ct: ClassType): CIR.ClassDef = { + private def rec(ct: ClassType)(implicit tm: TypeMapping): CIR.ClassDef = { // Convert the whole class hierarchy to register all siblings, in a top down fasion, that way // each children class in the the CIR hierarchy get registered to its parent and we can keep track // of all of them. @@ -422,7 +455,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe translation(ct) } - private def rec(e: Expr)(implicit env: Env): CIR.Expr = e match { + private def rec(e: Expr)(implicit env: Env, tm0: TypeMapping): CIR.Expr = e match { case UnitLiteral() => CIR.Lit(L.UnitLit) case BooleanLiteral(v) => CIR.Lit(L.BoolLit(v)) case IntLiteral(v) => CIR.Lit(L.IntLit(v)) @@ -447,8 +480,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case FunctionInvocation(tfd, args0) => val fun = rec(tfd) - val extra = ctxDB(tfd.fd) map convertVarInfoToParam - val args = args0 map rec + implicit val tm1 = tm0 ++ tfd.typesMap + val extra = ctxDB(tfd.fd) map { c => convertVarInfoToParam(c)(tm1) } + val args = args0 map { a0 => rec(a0)(env, tm1) } CIR.App(fun, extra, args) @@ -499,11 +533,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe CIR.ArrayInit(alloc) - case NonemptyArray(_, Some(_)) => internalError("Inconsistent state of NonemptyArray") - case array @ NonemptyArray(elems, None) => // Here elems is non-empty val arrayType = rec(array.getType).asInstanceOf[CIR.ArrayType] @@ -518,7 +550,6 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe val alloc = CIR.ArrayAllocStatic(arrayType, values.length, values) CIR.ArrayInit(alloc) - case IfExpr(cond, thenn, NoTree(_)) => CIR.If(rec(cond), rec(thenn)) case IfExpr(cond, thenn, elze) => CIR.IfElse(rec(cond), rec(thenn), rec(elze)) diff --git a/src/main/scala/leon/purescala/Definitions.scala b/src/main/scala/leon/purescala/Definitions.scala index eb89defb4..35c84cb91 100644 --- a/src/main/scala/leon/purescala/Definitions.scala +++ b/src/main/scala/leon/purescala/Definitions.scala @@ -572,7 +572,7 @@ object Definitions { } } - private lazy val typesMap: Map[TypeParameter, TypeTree] = { + lazy val typesMap: Map[TypeParameter, TypeTree] = { (fd.typeArgs zip tps).toMap.filter(tt => tt._1 != tt._2) } From d151c31feb8a5224043fed7e89d2330deb7b8f3c Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 17 Nov 2016 16:08:57 +0100 Subject: [PATCH 27/77] Remove useless optimisation of id translation This can also prevent some clashes with C std functions. --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 2ee4e3129..77ca83188 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -338,11 +338,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe ****************************************************************************************************/ private def rec(id: Identifier): CIR.Id = { if (id.name == "_main") "_main" - else { - val uniqueId = id.uniqueNameDelimited("_") - if (uniqueId endsWith "_0") id.name // when possible, keep the id as clean as possible - else uniqueId - } + else id.uniqueNameDelimited("_") } // Try first to fetch the function from cache to handle recursive funcitons. From 0472cd583b630ad377b31846e722892cc7bb7dfa Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 17 Nov 2016 17:51:37 +0100 Subject: [PATCH 28/77] Update regression test about invalid pattern matching Xlang now ensures no side effect so we remove this test from GenC. --- ...ng1.scala => InvalidPatternMatching.scala} | 4 +- .../invalid/InvalidPatternMatching2.scala | 60 ------------------- 2 files changed, 2 insertions(+), 62 deletions(-) rename src/test/resources/regression/genc/invalid/{InvalidPatternMatching1.scala => InvalidPatternMatching.scala} (93%) delete mode 100644 src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala diff --git a/src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala b/src/test/resources/regression/genc/invalid/InvalidPatternMatching.scala similarity index 93% rename from src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala rename to src/test/resources/regression/genc/invalid/InvalidPatternMatching.scala index e584fcbbc..f1da15df1 100644 --- a/src/test/resources/regression/genc/invalid/InvalidPatternMatching1.scala +++ b/src/test/resources/regression/genc/invalid/InvalidPatternMatching.scala @@ -3,6 +3,7 @@ import leon.annotation.extern import leon.lang._ +// Unapply pattern is not supported by GenC object Odd { def unapply(p: (Int, Int)): Option[Int] = { val x = p._1 @@ -10,7 +11,7 @@ object Odd { } } -object InvalidPatternMatching1 { +object InvalidPatternMatching { def test(x: Option[(Int, Int)]) = x match { case None() => 0 @@ -57,4 +58,3 @@ object InvalidPatternMatching1 { } - diff --git a/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala b/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala deleted file mode 100644 index 4f8897ed6..000000000 --- a/src/test/resources/regression/genc/invalid/InvalidPatternMatching2.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -import leon.annotation.extern -import leon.lang._ - -case class Pair2(var x: Int, var y: Int) - -object Odd2 { - def unapply(p: Pair2): Option[Int] = { - p.y = p.y + 1 // WITHOUT THIS IT DOESN'T CRASH - val x = p.x - if (x == 1) Some(x) else None[Int] - } -} - -object InvalidPatternMatching2 { - - def test2(p: Pair2) = { - require(p.y == 0) - p match { - case Odd2(x) => p.y - case _ => 0 - } - } ensuring { res => res == p.y && p.y == 1 } - - def power2(pow: Int): Int = { - require(pow < 31 && pow >= 0) - - var res = 1 - var n = pow - - while (n > 0) { - res = res * 2 - } - - res - } - - def _main() = { - var testCount = 0 - - def expect(value: Int, actual: Int): Int = { - require(testCount >= 0 && testCount < 30) - - testCount = testCount + 1 - - if (value == actual) 0 - else power2(testCount) - } - - expect(1, test2(Pair2(1, 0))) + - expect(0, test2(Pair2(0, 0))) - } ensuring { _ == 0 } - - @extern - def main(args: Array[String]): Unit = _main() - -} - - From 1dd62290dee0b286f1c7ab31e699f36649d2f27f Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 18 Nov 2016 18:23:18 +0100 Subject: [PATCH 29/77] Minor improvements of FIS/FOS Note: when using the private ctor of FIS/FOS, Leon will emit something like this: [info] [ Error ] Test.scala:93:16: Class leon.io.FileInputStream not defined? [info] val fis1 = FIS(Some("input.txt")) [info] ^^^ and therefore identifying the call to the constructor as faulty. --- library/leon/io/FileInputStream.scala | 7 +++---- library/leon/io/FileOutputStream.scala | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/leon/io/FileInputStream.scala b/library/leon/io/FileInputStream.scala index 9d5c92f56..a3d9bd677 100644 --- a/library/leon/io/FileInputStream.scala +++ b/library/leon/io/FileInputStream.scala @@ -39,8 +39,7 @@ object FileInputStream { leon.lang.Some[String](filename) } catch { case _: Throwable => leon.lang.None[String] - }, - 0 // nothing consumed yet + } ) } @@ -48,7 +47,7 @@ object FileInputStream { @library @cCode.typedef(alias = "FILE*", include = "stdio.h") -case class FileInputStream(var filename: Option[String], var consumed: BigInt) { +case class FileInputStream private (var filename: Option[String], var consumed: BigInt = 0) { /** * Close the stream; return `true` on success. @@ -65,7 +64,7 @@ case class FileInputStream(var filename: Option[String], var consumed: BigInt) { |} """ ) - def close(implicit state: State): Boolean = { + def close()(implicit state: State): Boolean = { state.seed += 1 filename = leon.lang.None[String] diff --git a/library/leon/io/FileOutputStream.scala b/library/leon/io/FileOutputStream.scala index 51866b142..79a3872cb 100644 --- a/library/leon/io/FileOutputStream.scala +++ b/library/leon/io/FileOutputStream.scala @@ -49,7 +49,7 @@ object FileOutputStream { @library @cCode.typedef(alias = "FILE*", include = "stdio.h") -case class FileOutputStream(var filename: Option[String]) { +case class FileOutputStream private (var filename: Option[String]) { /** * Close the stream; return `true` on success. From 8e015bfc1ca66eb211ab8100490a340edf170768 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sun, 20 Nov 2016 17:55:58 +0100 Subject: [PATCH 30/77] Cosmetic update --- src/test/resources/regression/genc/unverified/IO.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/IO.scala b/src/test/resources/regression/genc/unverified/IO.scala index e7384e1eb..c13a1790a 100644 --- a/src/test/resources/regression/genc/unverified/IO.scala +++ b/src/test/resources/regression/genc/unverified/IO.scala @@ -6,10 +6,9 @@ import leon.io.{ StdIn, StdOut } object IO { - def getFileName() = "test.txt" + def filename = "test.txt" def printX(x: Int, c: Char, sep: String): Unit = { - val filename = getFileName val out = FOS.open(filename) if (out.isOpen) { out.write(x) @@ -43,7 +42,7 @@ object IO { val out = FOS.open("echo.txt") if (out.isOpen) { out.write(message) - out.close + out.close() () } else { @@ -53,7 +52,7 @@ object IO { val in = FIS.open("echo.txt") if (in.isOpen) { val x = in.readInt - in.close + in.close() if (x == message) { StdOut.print("The echo was slow but successful!\n") From 8460f96e2257e9d1088baf0c5db022bfc1510f59 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Sun, 20 Nov 2016 22:51:41 +0100 Subject: [PATCH 31/77] Implement tryReadInt & tweak some other I/O APIs --- library/leon/io/FileInputStream.scala | 90 ++++++++++++------- library/leon/io/StdIn.scala | 86 ++++++++++-------- .../regression/genc/unverified/IO.scala | 11 ++- .../regression/genc/valid/GuessNumberC.scala | 4 +- 4 files changed, 119 insertions(+), 72 deletions(-) diff --git a/library/leon/io/FileInputStream.scala b/library/leon/io/FileInputStream.scala index a3d9bd677..8dc50ecb8 100644 --- a/library/leon/io/FileInputStream.scala +++ b/library/leon/io/FileInputStream.scala @@ -3,7 +3,7 @@ package leon.io import leon.annotation._ -import leon.lang.Option +import leon.lang._ // See NOTEs in StdIn. // @@ -30,15 +30,14 @@ object FileInputStream { def open(filename: String)(implicit state: State): FileInputStream = { state.seed += 1 - // FIXME Importing leon.lang.Option doesn't mean it is imported, why? new FileInputStream( try { // Check whether the stream can be opened or not val out = new java.io.FileReader(filename) out.close() - leon.lang.Some[String](filename) + Some[String](filename) } catch { - case _: Throwable => leon.lang.None[String] + case _: Throwable => None[String] } ) } @@ -67,7 +66,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: def close()(implicit state: State): Boolean = { state.seed += 1 - filename = leon.lang.None[String] + filename = None[String] true // This implementation never fails } @@ -88,26 +87,52 @@ case class FileInputStream private (var filename: Option[String], var consumed: filename.isDefined } + /** + * Read the next signed decimal integer + * + * NOTE on failure, 0 is returned + */ @library - @cCode.function(code = """ - |int32_t __FUNCTION__(FILE* this, void* unused) { - | int32_t x; - | fscanf(this, "%"SCNd32, &x); - | return x; - |} - """ - ) - def readInt(implicit state: State): Int = { + def readInt()(implicit state: State): Int = { require(isOpen) - state.seed += 1 - nativeReadInt(state.seed) + tryReadInt() getOrElse 0 + } + + /** + * Attempt to read the next signed decimal integer + */ + @library + def tryReadInt()(implicit state: State): Option[Int] = { + require(isOpen) + + var valid = true; + + // Because this is a nested function, contexts variables are passes by reference. + @cCode.function(code = """ + |int32_t __FUNCTION__(FILE** this, void** unused, bool* valid) { + | int32_t x; + | *valid = fscanf(*this, "%"SCNd32, &x) == 1; + | return x; + |} + """, + includes = "inttypes.h" + ) + def impl(): Int = { + state.seed += 1 + val (check, value) = nativeReadInt(state.seed) + valid = check + value + } + + val res = impl() + if (valid) Some(res) else None() } // Implementation detail @library @extern @cCode.drop - private def nativeReadInt(seed: BigInt): Int = { + private def nativeReadInt(seed: BigInt): (Boolean, Int) = { /* WARNING This code is singificantly a duplicate of leon.io.StdIn.nativeReadInt * because there's no clean way to refactor this in Leon's library. * @@ -148,19 +173,21 @@ case class FileInputStream private (var filename: Option[String], var consumed: // Skip what was already consumed by previous reads assert(in.skip(consumed.toLong) == consumed.toLong) // Yes, skip might not skip the requested number of bytes... - // Handle error in parsing and close the stream, return the given error code - def fail(e: Int): Int = { + // Handle error in parsing and close the stream + def fail(): (Boolean, Int) = { in.close() - e // TODO throw an exception and change `e` for a decent error message + (false, 0) } // Handle success (nothing to do actually) and close the stream - def success(x: Int): Int = { + def success(x: Int): (Boolean, Int) = { in.close() - x + (true, x) } - // Match C99 isspace function: either space (0x20), form feed (0x0c), line feed (0x0a), carriage return (0x0d), horizontal tab (0x09) or vertical tab (0x0b) + // Match C99 isspace function: + // either space (0x20), form feed (0x0c), line feed (0x0a), carriage return (0x0d), + // horizontal tab (0x09) or vertical tab (0x0b) def isSpace(c: Int): Boolean = Set(0x20, 0x0c, 0x0a, 0x0d, 0x09, 0x0b) contains c // Digit base 10 @@ -187,26 +214,25 @@ case class FileInputStream private (var filename: Option[String], var consumed: // Read as many digit as possible, and after each digit we mark // the stream to simulate a "peek" at the next, possibly non-digit, // character on the stream. - def readDecInt(acc: Int, mark: Boolean): Int = { + def readDecInt(acc: Int, mark: Boolean): (Boolean, Int) = { if (mark) markStream() val c = read() if (isDigit(c)) readDecInt(safeAdd(acc, c), true) else if (mark) success(acc) // at least one digit was processed - else fail(-2) // no digit was processed + else fail() // no digit was processed } val first = skipSpaces() first match { - case EOF => fail(-1) - case '-' => - readDecInt(0, false) - case '+' => readDecInt(0, false) - case c if isDigit(c) => readDecInt(c - '0', true) - case _ => fail(-3) + case EOF => fail() + case '-' => val (c, x) = readDecInt(0, false); (c, -x) + case '+' => readDecInt(0, false) + case c if isDigit(c) => readDecInt(c - '0', true) + case _ => fail() } - } ensuring((x: Int) => true) - + } } diff --git a/library/leon/io/StdIn.scala b/library/leon/io/StdIn.scala index c2ee6fb33..92325172b 100644 --- a/library/leon/io/StdIn.scala +++ b/library/leon/io/StdIn.scala @@ -3,6 +3,7 @@ package leon.io import leon.annotation._ +import leon.lang._ /* * NOTEs for GenC: @@ -19,12 +20,6 @@ import leon.annotation._ * - Currently, GenC doesn't support `BigInt` which implies that `readBigInt` is * dropped as well. * - * - In order to handle errors (e.g. EOF or invalid formatting), the API has to be - * updated. One solution would be to return `Option`s. Another would be to use - * exception, however this is probably trickier to translate to C. - * - * - Decisions regarding those issues should be applied to FileInputStream as well. - * * * FIXME Using the `scala.io.StdIn.read*` methods has a major shortcomming: * the data is read from a entire line! @@ -33,28 +28,47 @@ import leon.annotation._ object StdIn { /** - * Reads the next signed decimal integer + * Read the next signed decimal integer, defaulting to 0 on failure + */ + @library + def readInt()(implicit state: State): Int = { + tryReadInt() getOrElse 0 + } + + /** + * Read the next signed decimal integer * * TODO to support other integer bases, one has to use SCNi32 in C. */ @library - @cCode.function(code = """ - |int32_t __FUNCTION__(void* unused) { - | int32_t x; - | scanf("%"SCNd32, &x); - | return x; - |} - """ - ) - def readInt(implicit state: State): Int = { - state.seed += 1 - nativeReadInt(state.seed) + def tryReadInt()(implicit state: State): Option[Int] = { + var valid = true; + + // Because this is a nested function, contexts variables are passes by reference. + @cCode.function(code = """ + |int32_t __FUNCTION__(void** unused, bool* valid) { + | int32_t x; + | *valid = scanf("%"SCNd32, &x) == 1; + | return x; + |} + """, + includes = "inttypes.h" + ) + def impl(): Int = { + state.seed += 1 + val (check, value) = nativeReadInt(state.seed) + valid = check + value + } + + val res = impl() + if (valid) Some(res) else None() } @library @extern @cCode.drop - private def nativeReadInt(seed: BigInt): Int = { + private def nativeReadInt(seed: BigInt): (Boolean, Int) = { /* WARNING This code is singificantly a duplicate of leon.io.FileInputStream.nativeReadInt * because there's no clean way to refactor this in Leon's library. * @@ -81,22 +95,24 @@ object StdIn { assert(in.markSupported()) def markStream() = in.mark(Int.MaxValue) - // Handle error in parsing and restore the stream, return the given error code - def fail(e: Int): Int = { + // Handle error in parsing and restore the stream + def fail(): (Boolean, Int) = { in.reset() - e // TODO throw an exception and change `e` for a decent error message + (false, 0) } // Handle success, but also restore stream in case we peeked at the next character - def success(x: Int): Int = { + def success(x: Int): (Boolean, Int) = { in.reset() - x + (true, x) } // Mark the stream now, so that in case of formatting error we can cancel the read markStream() - // Match C99 isspace function: either space (0x20), form feed (0x0c), line feed (0x0a), carriage return (0x0d), horizontal tab (0x09) or vertical tab (0x0b) + // Match C99 isspace function: + // either space (0x20), form feed (0x0c), line feed (0x0a), carriage return (0x0d), + // horizontal tab (0x09) or vertical tab (0x0b) def isSpace(c: Int): Boolean = Set(0x20, 0x0c, 0x0a, 0x0d, 0x09, 0x0b) contains c // Digit base 10 @@ -123,29 +139,29 @@ object StdIn { // Read as many digit as possible, and after each digit we mark // the stream to simulate a "peek" at the next, possibly non-digit, // character on the stream. - def readDecInt(acc: Int, mark: Boolean): Int = { + def readDecInt(acc: Int, mark: Boolean): (Boolean, Int) = { if (mark) markStream() val c = in.read() if (isDigit(c)) readDecInt(safeAdd(acc, c), true) else if (mark) success(acc) // at least one digit was processed - else fail(-2) // no digit was processed + else fail() // no digit was processed } val first = skipSpaces() first match { - case EOF => fail(-1) - case '-' => - readDecInt(0, false) - case '+' => readDecInt(0, false) - case c if isDigit(c) => readDecInt(c - '0', true) - case _ => fail(-3) + case EOF => fail() + case '-' => val (c, x) = readDecInt(0, false); (c, -x) + case '+' => readDecInt(0, false) + case c if isDigit(c) => readDecInt(c - '0', true) + case _ => fail() } - } ensuring((x: Int) => true) + } @library @cCode.drop - def readBigInt(implicit state: State): BigInt = { + def readBigInt()(implicit state: State): BigInt = { state.seed += 1 nativeReadBigInt(state.seed) } @@ -159,7 +175,7 @@ object StdIn { @library @cCode.drop - def readBoolean(implicit state: State): Boolean = { + def readBoolean()(implicit state: State): Boolean = { state.seed += 1 nativeReadBoolean(state.seed) } diff --git a/src/test/resources/regression/genc/unverified/IO.scala b/src/test/resources/regression/genc/unverified/IO.scala index c13a1790a..e250d75f5 100644 --- a/src/test/resources/regression/genc/unverified/IO.scala +++ b/src/test/resources/regression/genc/unverified/IO.scala @@ -3,6 +3,7 @@ import leon.annotation._ import leon.io.{ FileOutputStream => FOS, FileInputStream => FIS } import leon.io.{ StdIn, StdOut } +import leon.lang._ object IO { @@ -29,8 +30,10 @@ object IO { def echo(): Unit = { implicit val state = leon.io.newState StdOut.print("ECHOING...") - val x = StdIn.readInt - StdOut.print(x) + StdIn.tryReadInt() match { + case Some(x) => StdOut.print(x) + case None() => StdOut.print("Nothing to echo") + } StdOut.print("\n") } @@ -51,10 +54,10 @@ object IO { val in = FIS.open("echo.txt") if (in.isOpen) { - val x = in.readInt + val x = in.tryReadInt() in.close() - if (x == message) { + if (x.isDefined && x.get == message) { StdOut.print("The echo was slow but successful!\n") } else StdOut.print("The echo was slow and buggy! :-(\n") diff --git a/src/test/resources/regression/genc/valid/GuessNumberC.scala b/src/test/resources/regression/genc/valid/GuessNumberC.scala index c6347b4c8..32f5606ee 100644 --- a/src/test/resources/regression/genc/valid/GuessNumberC.scala +++ b/src/test/resources/regression/genc/valid/GuessNumberC.scala @@ -56,7 +56,9 @@ object GuessNumberC { StdOut.print(guess) StdOut.print(" [0 == false, 1 == true]: ") - val input = StdIn.readInt + // Here we are not at a beauty contest; if the input is invalid or EOF is reached, + // we simply pick "0", whatever its actual meaning. + val input = StdIn.tryReadInt getOrElse 0 StdOut.println() From a5f5a76df6e367a1b7c43951d829eddc9e274552 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 1 Dec 2016 14:30:21 +0100 Subject: [PATCH 32/77] Add support for Byte in GenC --- src/main/scala/leon/genc/CAST.scala | 7 ++- src/main/scala/leon/genc/CPrinter.scala | 3 + src/main/scala/leon/genc/ir/IR.scala | 7 +++ src/main/scala/leon/genc/ir/IRPrinter.scala | 1 + src/main/scala/leon/genc/ir/Literals.scala | 10 +++- src/main/scala/leon/genc/ir/Normaliser.scala | 2 +- .../scala/leon/genc/ir/PrimitiveTypes.scala | 9 ++- src/main/scala/leon/genc/ir/Transformer.scala | 1 + src/main/scala/leon/genc/ir/Visitor.scala | 1 + .../scala/leon/genc/phases/IR2CPhase.scala | 11 +++- .../leon/genc/phases/Scala2IRPhase.scala | 21 ++++++- .../regression/genc/valid/Bytes.scala | 60 +++++++++++++++++++ 12 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/Bytes.scala diff --git a/src/main/scala/leon/genc/CAST.scala b/src/main/scala/leon/genc/CAST.scala index 1ece0da09..ff15cfa4b 100644 --- a/src/main/scala/leon/genc/CAST.scala +++ b/src/main/scala/leon/genc/CAST.scala @@ -207,6 +207,9 @@ object CAST { // C Abstract Syntax Tree require(value.isValue) } + // This can represent any C cast, however unsafe it can be. + case class Cast(expr: Expr, typ: Type) extends Expr + /* ---------------------------------------------------------- Helpers ----- */ // Flatten blocks together and remove `()` literals @@ -234,7 +237,7 @@ object CAST { // C Abstract Syntax Tree val body = buildBlock( if (returnInt) Return(Call(_mainId, Nil)) :: Nil - else Call(_mainId, Nil) :: Return(Lit(IntLit(0))) :: Nil + else Call(_mainId, Nil) :: Return(Lit(Int32Lit(0))) :: Nil ) val main = Fun(id, retType, params, Left(body)) @@ -270,7 +273,7 @@ object CAST { // C Abstract Syntax Tree def isValue = e match { case _: Binding | _: Lit | _: EnumLiteral | _: StructInit | _: UnionInit | _: Call | _: FieldAccess | _: ArrayAccess | - _: Ref | _: Deref | _: BinOp | _: UnOp => true + _: Ref | _: Deref | _: BinOp | _: UnOp | _: Cast => true case _ => false } diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala index 1296c1d49..0519cffdc 100644 --- a/src/main/scala/leon/genc/CPrinter.scala +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -64,6 +64,7 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { case Primitive(pt) => pt match { case CharType => c"char" + case Int8Type => c"int8_t" case Int32Type => c"int32_t" case BoolType => c"bool" case UnitType => c"void" @@ -167,6 +168,8 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { case Break => c"break" case Return(value) => c"return $value" + + case Cast(expr, typ) => optP { c"($typ)$expr" } } private[genc] def pp(wt: WrapperTree)(implicit ctx: PrinterContext): Unit = wt match { diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index c40a3d078..f7187b0f9 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -183,6 +183,7 @@ private[genc] sealed trait IR { ir => case While(_, _) => NoType case IsA(_, _) => PrimitiveType(BoolType) case AsA(_, ct) => ct + case IntegralCast(_, newIntegralType) => PrimitiveType(newIntegralType) case Lit(lit) => PrimitiveType(lit.getPrimitiveType) case Ref(e) => ReferenceType(e.getType) case Deref(e) => e.getType.asInstanceOf[ReferenceType].t @@ -245,6 +246,11 @@ private[genc] sealed trait IR { ir => case class IsA(expr: Expr, ct: ClassType) extends Expr case class AsA(expr: Expr, ct: ClassType) extends Expr + // Integer narrowing + widening casts + case class IntegralCast(expr: Expr, newType: IntegralPrimitiveType) extends Expr { + require(expr.getType.isIntegral) + } + // Literals case class Lit(lit: Literal) extends Expr @@ -375,6 +381,7 @@ private[genc] sealed trait IR { ir => def repId(typ: Type): Id = typ match { case PrimitiveType(pt) => pt match { case CharType => "char" + case Int8Type => "int8" case Int32Type => "int32" case BoolType => "bool" case UnitType => "unit" diff --git a/src/main/scala/leon/genc/ir/IRPrinter.scala b/src/main/scala/leon/genc/ir/IRPrinter.scala index e9bf04813..98521d636 100644 --- a/src/main/scala/leon/genc/ir/IRPrinter.scala +++ b/src/main/scala/leon/genc/ir/IRPrinter.scala @@ -101,6 +101,7 @@ final class IRPrinter[S <: IR](val ir: S) { "while (" + rec(cond) + ") {" + ptx.newLine + " " + rec(body)(ptx + 1) + ptx.newLine + "}" case IsA(expr, ct) => "¿" + ct.clazz.id + "?" + rec(expr) case AsA(expr, ct) => "(" + ct.clazz.id + ")" + rec(expr) + case IntegralCast(expr, newType) => "(" + newType + ")" + rec(expr) case Lit(lit) => lit.toString case Ref(e) => "&" + rec(e) case Deref(e) => "*" + rec(e) diff --git a/src/main/scala/leon/genc/ir/Literals.scala b/src/main/scala/leon/genc/ir/Literals.scala index 3cfa9c5bf..d0d90cb2d 100644 --- a/src/main/scala/leon/genc/ir/Literals.scala +++ b/src/main/scala/leon/genc/ir/Literals.scala @@ -14,7 +14,8 @@ private[genc] object Literals { sealed abstract class Literal { def getPrimitiveType: PrimitiveType = this match { case CharLit(_) => CharType - case IntLit(_) => Int32Type + case Int8Lit(_) => Int8Type + case Int32Lit(_) => Int32Type case BoolLit(_) => BoolType case UnitLit => UnitType case StringLit(_) => StringType @@ -22,7 +23,8 @@ private[genc] object Literals { override def toString: String = this match { case CharLit(v) => "'" + escape(v) + "'" - case IntLit(v) => s"$v" + case Int8Lit(v) => s"$v" + case Int32Lit(v) => s"$v" case BoolLit(v) => s"$v" case UnitLit => s"()" case StringLit(v) => '"' + escape(v) + '"' @@ -31,7 +33,9 @@ private[genc] object Literals { case class CharLit(v: Char) extends Literal - case class IntLit(v: Int) extends Literal + case class Int8Lit(v: Byte) extends Literal + + case class Int32Lit(v: Int) extends Literal case class BoolLit(v: Boolean) extends Literal diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala index d064d0829..8d34399b4 100644 --- a/src/main/scala/leon/genc/ir/Normaliser.scala +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -33,7 +33,7 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with private def recCT(typ: ClassType)(implicit env: Env) = rec(typ).asInstanceOf[to.ClassType] override def recImpl(e: Expr)(implicit env: Env): (to.Expr, Env) = e match { - case _: Binding | _: Lit | _: Block | _: Deref => super.recImpl(e) + case _: Binding | _: Lit | _: Block | _: Deref | _: IntegralCast => super.recImpl(e) case DeclInit(vd0, ArrayInit(alloc0)) => val vd = rec(vd0) diff --git a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala index a9e3a72e6..fb9971e05 100644 --- a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala +++ b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala @@ -16,13 +16,16 @@ private[genc] object PrimitiveTypes { } def isIntegral: Boolean = this match { - case CharType | Int32Type => true + case _: IntegralPrimitiveType => true case _ => false } } - case object CharType extends PrimitiveType - case object Int32Type extends PrimitiveType + sealed abstract class IntegralPrimitiveType extends PrimitiveType + + case object CharType extends IntegralPrimitiveType + case object Int8Type extends IntegralPrimitiveType + case object Int32Type extends IntegralPrimitiveType case object BoolType extends PrimitiveType case object UnitType extends PrimitiveType case object StringType extends PrimitiveType diff --git a/src/main/scala/leon/genc/ir/Transformer.scala b/src/main/scala/leon/genc/ir/Transformer.scala index 62bd02947..91934edaf 100644 --- a/src/main/scala/leon/genc/ir/Transformer.scala +++ b/src/main/scala/leon/genc/ir/Transformer.scala @@ -132,6 +132,7 @@ abstract class Transformer[From <: IR, To <: IR](final val from: From, final val case While(cond, body) => to.While(rec(cond), rec(body)) -> env case IsA(expr, ct) => to.IsA(rec(expr), to.ClassType(rec(ct.clazz))) -> env case AsA(expr, ct) => to.AsA(rec(expr), to.ClassType(rec(ct.clazz))) -> env + case IntegralCast(expr, t) => to.IntegralCast(rec(expr), t) -> env case Lit(lit) => to.Lit(lit) -> env case Ref(e) => to.Ref(rec(e)) -> env case Deref(e) => to.Deref(rec(e)) -> env diff --git a/src/main/scala/leon/genc/ir/Visitor.scala b/src/main/scala/leon/genc/ir/Visitor.scala index 262cdef4d..9bb3a3789 100644 --- a/src/main/scala/leon/genc/ir/Visitor.scala +++ b/src/main/scala/leon/genc/ir/Visitor.scala @@ -107,6 +107,7 @@ abstract class Visitor[S <: IR](final val ir: S) { case While(cond, body) => rec(cond); rec(body) case IsA(expr, ct) => rec(expr); rec(ct.clazz) case AsA(expr, ct) => rec(expr); rec(ct.clazz) + case IntegralCast(expr, _) => rec(expr) case Lit(lit) => () case Ref(e) => rec(e) case Deref(e) => rec(e) diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala index 2c695266c..9dd21570e 100644 --- a/src/main/scala/leon/genc/phases/IR2CPhase.scala +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -134,7 +134,7 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { val bufferId = C.FreshId("buffer") val bufferDecl = C.DeclArrayStatic(bufferId, rec(arrayType.base), length, values map rec) val data = C.Binding(bufferId) - val len = C.Lit(IntLit(length)) + val len = C.Lit(Int32Lit(length)) val array = array2Struct(arrayType) val varInit = C.StructInit(array, data :: len :: Nil) val varDecl = C.DeclInit(rec(vd.id), array, varInit) @@ -199,6 +199,11 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { val unionAccess = C.FieldAccess(rec(expr), TaggedUnion.value) C.FieldAccess(unionAccess, fieldId) + case IntegralCast(expr0, newType0) => + val expr = rec(expr0) + val newType = rec(PrimitiveType(newType0)) + C.Cast(expr, newType) + case Lit(lit) => C.Lit(lit) case Ref(e) => C.Ref(rec(e)) @@ -281,7 +286,7 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { // Check whether an extra byte was added to the structure val args = - if (markedAsEmpty(cd)) Seq(Lit(IntLit(0))) + if (markedAsEmpty(cd)) Seq(Lit(Int8Lit(0))) else args0 C.StructInit(struct, args map rec) @@ -385,7 +390,7 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { warning(s"Empty structures are not allowed according to the C99 standard. " + s"I'm adding a dummy byte to ${cd.id} structure for compatibility purposes.") markAsEmpty(cd) - Seq(C.Var(C.Id("extra"), C.Primitive(CharType))) + Seq(C.Var(C.Id("extra"), C.Primitive(Int8Type))) } else cd.fields map rec C.Struct(rec(cd.id), fields) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 77ca83188..ad24a1ba4 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -185,6 +185,18 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case _ => internalError("Unexpected ${typ.getClass} instead of TupleType") } + private def buildCast(e0: Expr, newType0: BitVectorType)(implicit env: Env, tm: TypeMapping): CIR.IntegralCast = { + val newType = newType0.size match { + case 8 => PT.Int8Type + case 32 => PT.Int32Type + case s => internalError("Unsupported integral cast to $s-bit integer") + } + + val e = rec(e0) + + CIR.IntegralCast(e, newType) + } + private def castNotSupported(ct: ClassType): Boolean = ct.classDef.isAbstract && ct.classDef.hasParent @@ -392,6 +404,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe private def rec(typ: TypeTree)(implicit tm: TypeMapping): CIR.Type = typ match { case UnitType => CIR.PrimitiveType(PT.UnitType) case BooleanType => CIR.PrimitiveType(PT.BoolType) + case Int8Type => CIR.PrimitiveType(PT.Int8Type) case Int32Type => CIR.PrimitiveType(PT.Int32Type) case CharType => CIR.PrimitiveType(PT.CharType) case StringType => CIR.PrimitiveType(PT.StringType) @@ -454,7 +467,8 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe private def rec(e: Expr)(implicit env: Env, tm0: TypeMapping): CIR.Expr = e match { case UnitLiteral() => CIR.Lit(L.UnitLit) case BooleanLiteral(v) => CIR.Lit(L.BoolLit(v)) - case IntLiteral(v) => CIR.Lit(L.IntLit(v)) + case ByteLiteral(v) => CIR.Lit(L.Int8Lit(v)) + case IntLiteral(v) => CIR.Lit(L.Int32Lit(v)) case CharLiteral(v) => CIR.Lit(L.CharLit(v)) case StringLiteral(v) => CIR.Lit(L.StringLit(v)) @@ -514,7 +528,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe // Convert to VLA or normal array val alloc = rec(length0) match { - case CIR.Lit(L.IntLit(length)) => + case CIR.Lit(L.Int32Lit(length)) => val values = (0 until length) map { _ => value } // the same expression, != same runtime value CIR.ArrayAllocStatic(arrayType, length, values) @@ -576,6 +590,9 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs)(e.getPos) case BVLShiftRight(lhs, rhs) => fatalError("Operator >>> is not supported", e.getPos) + case BVWideningCast(e, t) => buildCast(e, t) + case BVNarrowingCast(e, t) => buildCast(e, t) + case MatchExpr(scrutinee, cases) => convertPatMap(scrutinee, cases) case IsInstanceOf(expr, ct) if castNotSupported(ct) => diff --git a/src/test/resources/regression/genc/valid/Bytes.scala b/src/test/resources/regression/genc/valid/Bytes.scala new file mode 100644 index 000000000..cd8459b6d --- /dev/null +++ b/src/test/resources/regression/genc/valid/Bytes.scala @@ -0,0 +1,60 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object Bytes { + + def fun() = { + val b: Byte = 127 + test(b) == 0 + }.holds + + def test(b: Byte) = { + require(b % 2 != 0) + if (b > 0) 0 else 1 + } + + def bar(x: Int) = { + require(x < 128) + x + } + + def gun(b: Byte): Int = { + assert(b >= -128 && b <= 127) // this is not a require! + bar(b) + } + + def hun(i: Int) = bar(i.toByte) + + def iun(b: Byte, c: Byte) = { + b + c + } ensuring { res => -256 <= res && res <= 254 } + + def _main(): Int = { + if (fun()) { + if (gun(42) == 42) { + if (hun(256 + 58) == 58) { + if (iun(-10, 10) == 0) { + + val b1: Byte = 0x01 + val b2: Byte = 0x03 + val or: Byte = (b1 | b2).toByte + val and: Byte = (b1 & b2).toByte + val xor: Byte = (b1 ^ b2).toByte + val neg: Byte = (~b1).toByte + + if (or == 0x03 && and == 0x01 && xor == 0x02 && neg == -0x02) 0 + else 1 + + } else 4 + } else 3 + } else 2 + } else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]) = _main() + +} + From 2516a3cf63388c49868a3fabe8cb86e177fb61f1 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Thu, 1 Dec 2016 15:52:17 +0100 Subject: [PATCH 33/77] Fix shift conversion to C - Use >>> in Scala to represent >> in C; - Cast signed integer to unsigned integer before shifting, and cast result back to signed integer afterward (see NOTE in code for rational) --- src/main/scala/leon/genc/CPrinter.scala | 1 + src/main/scala/leon/genc/ir/IR.scala | 1 + .../scala/leon/genc/ir/PrimitiveTypes.scala | 1 + .../scala/leon/genc/phases/IR2CPhase.scala | 15 +++++++++++++ .../leon/genc/phases/Scala2IRPhase.scala | 4 ++-- .../genc/unverified/IntegralColor.scala | 4 ++-- .../regression/genc/valid/Shifts.scala | 22 +++++++++++++++++++ .../verification/xlang/IntegralColor.scala | 4 ++-- 8 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/Shifts.scala diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala index 0519cffdc..a27c2ae67 100644 --- a/src/main/scala/leon/genc/CPrinter.scala +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -66,6 +66,7 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { case CharType => c"char" case Int8Type => c"int8_t" case Int32Type => c"int32_t" + case UInt32Type => c"uint32_t" case BoolType => c"bool" case UnitType => c"void" case StringType => c"char*" diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index f7187b0f9..f57f75ef8 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -383,6 +383,7 @@ private[genc] sealed trait IR { ir => case CharType => "char" case Int8Type => "int8" case Int32Type => "int32" + case UInt32Type => "uint32" case BoolType => "bool" case UnitType => "unit" case StringType => "string" diff --git a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala index fb9971e05..3bf25389e 100644 --- a/src/main/scala/leon/genc/ir/PrimitiveTypes.scala +++ b/src/main/scala/leon/genc/ir/PrimitiveTypes.scala @@ -25,6 +25,7 @@ private[genc] object PrimitiveTypes { case object CharType extends IntegralPrimitiveType case object Int8Type extends IntegralPrimitiveType + case object UInt32Type extends IntegralPrimitiveType case object Int32Type extends IntegralPrimitiveType case object BoolType extends PrimitiveType case object UnitType extends PrimitiveType diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala index 9dd21570e..93024d720 100644 --- a/src/main/scala/leon/genc/phases/IR2CPhase.scala +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -167,6 +167,21 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { case ArrayLength(array) => C.FieldAccess(rec(array), C.Id("length")) case Assign(lhs, rhs) => C.Assign(rec(lhs), rec(rhs)) + + /* NOTE For shift operators, we first cast to unsigned, perform the shift operation and + * finally cast it back to signed integer. The rational is that overflow is undefined + * behaviour in C99 on signed integers and shifts of negative integers is also undefined + * behaviour. However, everything is well defined over unsigned integers. The catch is + * that casting back from unsigned to signed integer is implementation defined for + * values that are negative if we read them using a 2's complement notation. Having a + * C compiler that does a normal wrap around is one of the requirement of GenC. + */ + case BinOp(op, lhs0, rhs) if Set[Operator](BLeftShift, BRightShift) contains op => + assert(lhs0.getType == PrimitiveType(Int32Type)) + val lhs = C.Cast(rec(lhs0), C.Primitive(UInt32Type)) + val expr = C.BinOp(op, lhs, rec(rhs)) // rhs doesn't need to be casted. + C.Cast(expr, C.Primitive(Int32Type)) + case BinOp(op, lhs, rhs) => C.BinOp(op, rec(lhs), rec(rhs)) case UnOp(op, expr) => C.UnOp(op, rec(expr)) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index ad24a1ba4..c33111bd3 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -587,8 +587,8 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case BVOr(lhs, rhs) => buildBinOp(lhs, O.BOr, rhs)(e.getPos) case BVXOr(lhs, rhs) => buildBinOp(lhs, O.BXor, rhs)(e.getPos) case BVShiftLeft(lhs, rhs) => buildBinOp(lhs, O.BLeftShift, rhs)(e.getPos) - case BVAShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs)(e.getPos) - case BVLShiftRight(lhs, rhs) => fatalError("Operator >>> is not supported", e.getPos) + case BVAShiftRight(lhs, rhs) => fatalError("Operator >> is not supported", e.getPos) + case BVLShiftRight(lhs, rhs) => buildBinOp(lhs, O.BRightShift, rhs)(e.getPos) case BVWideningCast(e, t) => buildCast(e, t) case BVNarrowingCast(e, t) => buildCast(e, t) diff --git a/src/test/resources/regression/genc/unverified/IntegralColor.scala b/src/test/resources/regression/genc/unverified/IntegralColor.scala index ef108ee07..28e7a23d3 100644 --- a/src/test/resources/regression/genc/unverified/IntegralColor.scala +++ b/src/test/resources/regression/genc/unverified/IntegralColor.scala @@ -8,11 +8,11 @@ object IntegralColor { def isValidComponent(x: Int) = x >= 0 && x <= 255 def getRed(rgb: Int): Int = { - (rgb & 0x00FF0000) >> 16 + (rgb & 0x00FF0000) >>> 16 } ensuring isValidComponent _ def getGreen(rgb: Int): Int = { - (rgb & 0x0000FF00) >> 8 + (rgb & 0x0000FF00) >>> 8 } ensuring isValidComponent _ def getBlue(rgb: Int): Int = { diff --git a/src/test/resources/regression/genc/valid/Shifts.scala b/src/test/resources/regression/genc/valid/Shifts.scala new file mode 100644 index 000000000..93f53b455 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Shifts.scala @@ -0,0 +1,22 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object Shifts { + + // Remember from C: 1 << 31 is undefined but 1u << 31 is well defined. + def _main(): Int = { + val x = 0x1 + val y = 31 + + val z = (x << y) >>> y + + if (z == x) 0 else 1 + } + + @extern + def main(args: Array[String]) = _main() + +} + diff --git a/testcases/verification/xlang/IntegralColor.scala b/testcases/verification/xlang/IntegralColor.scala index 6d25fd056..6458629c3 100644 --- a/testcases/verification/xlang/IntegralColor.scala +++ b/testcases/verification/xlang/IntegralColor.scala @@ -8,11 +8,11 @@ object IntegralColor { def isValidComponent(x: Int) = x >= 0 && x <= 255 def getRed(rgb: Int): Int = { - (rgb & 0x00FF0000) >> 16 + (rgb & 0x00FF0000) >>> 16 } ensuring isValidComponent _ def getGreen(rgb: Int): Int = { - (rgb & 0x0000FF00) >> 8 + (rgb & 0x0000FF00) >>> 8 } ensuring isValidComponent _ def getBlue(rgb: Int): Int = { From e463dbb15005ca8730e72d16570fea336d455b98 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 2 Dec 2016 09:48:09 +0100 Subject: [PATCH 34/77] Add functions to StdIn/StdOut to read/write bytes --- library/leon/io/StdIn.scala | 59 +++++++++++++++++- library/leon/io/StdOut.scala | 35 +++++++++-- .../regression/genc/valid/ByteEcho.input | Bin 0 -> 512 bytes .../regression/genc/valid/ByteEcho.scala | 26 ++++++++ 4 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/ByteEcho.input create mode 100644 src/test/resources/regression/genc/valid/ByteEcho.scala diff --git a/library/leon/io/StdIn.scala b/library/leon/io/StdIn.scala index 92325172b..5f83ad8fd 100644 --- a/library/leon/io/StdIn.scala +++ b/library/leon/io/StdIn.scala @@ -20,13 +20,66 @@ import leon.lang._ * - Currently, GenC doesn't support `BigInt` which implies that `readBigInt` is * dropped as well. * + * - Using I/O features from stdio.h is forbidden by MISRA C 2012 rule 21.6. The I/O + * operations are provided here for debugging purposes and for illustration of @cCode + * annotations. + * + * - The C implementation of readByte and tryReadByte assume CHAR_BITS == 8. This is + * however not checked at runtime. + * * * FIXME Using the `scala.io.StdIn.read*` methods has a major shortcomming: * the data is read from a entire line! */ - object StdIn { + /** + * Read the next signed byte [-128,127], defaulting to 0 on EOF + */ + @library + def readByte()(implicit state: State): Byte = { + tryReadByte() getOrElse 0 + } + + /** + * Attempt to read the next signed byte [-128,127] + */ + @library + def tryReadByte()(implicit state: State): Option[Byte] = { + var valid = true + + // Similar to tryReadInt, but here we have to use %c to read a byte + // (which assumes CHAR_BITS == 8) because SCNi8 will read char '0' to '9' + // and not the "raw" data. + @cCode.function(code = """ + |int8_t __FUNCTION__(void** unused, bool* valid) { + | int8_t x; + | *valid = scanf("%c", &x) == 1; + | return x; + |} + """, + includes = "inttypes.h" + ) + def impl(): Byte = { + state.seed += 1 + val (check, value) = nativeReadByte(state.seed) + valid = check + value + } + + val res = impl() + if (valid) Some(res) else None() + } + + @library + @extern + @cCode.drop + private def nativeReadByte(seed: BigInt): (Boolean, Byte) = { + val b = Array[Byte](0) + val read = System.in.read(b) + if (read == 1) (true, b(0)) else (false, 0) + } + /** * Read the next signed decimal integer, defaulting to 0 on failure */ @@ -36,13 +89,13 @@ object StdIn { } /** - * Read the next signed decimal integer + * Attempt to read the next signed decimal integer * * TODO to support other integer bases, one has to use SCNi32 in C. */ @library def tryReadInt()(implicit state: State): Option[Int] = { - var valid = true; + var valid = true // Because this is a nested function, contexts variables are passes by reference. @cCode.function(code = """ diff --git a/library/leon/io/StdOut.scala b/library/leon/io/StdOut.scala index 2d2d5d44a..a11cafd13 100644 --- a/library/leon/io/StdOut.scala +++ b/library/leon/io/StdOut.scala @@ -15,7 +15,7 @@ object StdOut { |} """, includes = "stdio.h" - ) + ) def print(x: String): Unit = { scala.Predef.print(x) } @@ -23,7 +23,32 @@ object StdOut { @library def println(s: String): Unit = { print(s) - print('\n') + println() + } + + /** + * This is the symmetric function to StdIn.readByte; + * i.e. the same restrictions applies for GenC. + */ + @library + @extern + @cCode.function( + code = """ + |void __FUNCTION__(int8_t x) { + | printf("%c", x); + |} + """, + includes = "inttypes.h:stdio.h" + ) + def print(x: Byte): Unit = { + val b = Array[Byte](x) + System.out.write(b, 0, 1) + } + + @library + def println(x: Byte): Unit = { + print(x) + println() } @library @@ -35,7 +60,7 @@ object StdOut { |} """, includes = "inttypes.h:stdio.h" - ) + ) def print(x: Int): Unit = { scala.Predef.print(x) } @@ -43,7 +68,7 @@ object StdOut { @library def println(x: Int): Unit = { print(x) - print('\n') + println() } @library @@ -63,7 +88,7 @@ object StdOut { @library def println(c: Char): Unit = { print(c) - print('\n') + println() } @library diff --git a/src/test/resources/regression/genc/valid/ByteEcho.input b/src/test/resources/regression/genc/valid/ByteEcho.input new file mode 100644 index 0000000000000000000000000000000000000000..d52fe9651cbeb253e930f8ae17089e28b1663c3e GIT binary patch literal 512 zcmV+b0{{Jxg-gUb(MSHgMdAV#^dq$yB)=9lOl;2?hA` z@(|TehYH^W8ja39_oD^Ouka&w>1j5@mAg$JD%-j*gZg-aHcZSvhES?m>kJg=7dMp` zt>M_fn?hYdvAfU%y>%ATl^br`_?>Vd>gghg8R|ZCu{O+L*QN|bY ze&#?@I8mvh@%Af-tE;W#$+n5&oW$cv&pfaHchS)J6y^S^o{EvJM~gA^3 C2M7fK literal 0 HcmV?d00001 diff --git a/src/test/resources/regression/genc/valid/ByteEcho.scala b/src/test/resources/regression/genc/valid/ByteEcho.scala new file mode 100644 index 000000000..bd1b03b60 --- /dev/null +++ b/src/test/resources/regression/genc/valid/ByteEcho.scala @@ -0,0 +1,26 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ +import leon.io._ + +object ByteEcho { + + def _main(): Int = { + implicit val state = newState + + var b = StdIn.tryReadByte() + + while (b.isDefined) { + StdOut.print(b.get) + b = StdIn.tryReadByte() + } + + 0 + } + + @extern + def main(args: Array[String]) = _main() + +} + From c198ce0de980c1bce29f939539dc0bff3321ba7a Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 2 Dec 2016 10:12:17 +0100 Subject: [PATCH 35/77] Make GenCSuite robust against encoding issues Now the outputs are compared at a byte level, not at a string level (which can be invalid). --- src/test/scala/leon/genc/GenCSuite.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/scala/leon/genc/GenCSuite.scala b/src/test/scala/leon/genc/GenCSuite.scala index 2f1965d72..2d1cac714 100644 --- a/src/test/scala/leon/genc/GenCSuite.scala +++ b/src/test/scala/leon/genc/GenCSuite.scala @@ -318,8 +318,9 @@ class GenCSuite extends LeonRegressionSuite { // Evaluate both Scala and C programs, making sure their output matches private def evaluate(xCtx: ExtendedContext)(binaries: Seq[String]) = { - val cOuts = binaries map { bin => Source.fromFile(evaluateC(xCtx, bin)).getLines } - val scala = Source.fromFile(evaluateScala(xCtx)).getLines + // Read file contents as bytes to avoid encoding issues + val cOuts = binaries map { bin => Files.readAllBytes(evaluateC(xCtx, bin).toPath) } + val scala = Files.readAllBytes(evaluateScala(xCtx).toPath) // Compare outputs for { (c, bin) <- cOuts zip binaries } { From 8929200db566ad2d873330cc2131d1e3ec40dc68 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 2 Dec 2016 11:06:15 +0100 Subject: [PATCH 36/77] Add functions to File(Input|Output)Stream to read/write bytes --- library/leon/io/FileInputStream.scala | 79 +++++++++++++++++-- library/leon/io/FileOutputStream.scala | 30 +++++++ .../genc/valid/ByteEchoFromFile.scala | 40 ++++++++++ 3 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/ByteEchoFromFile.scala diff --git a/library/leon/io/FileInputStream.scala b/library/leon/io/FileInputStream.scala index 8dc50ecb8..b3c464e64 100644 --- a/library/leon/io/FileInputStream.scala +++ b/library/leon/io/FileInputStream.scala @@ -5,12 +5,13 @@ package leon.io import leon.annotation._ import leon.lang._ -// See NOTEs in StdIn. -// -// NOTE Don't attempt to create a FileInputStream directly. Use FileInputStream.open instead. -// -// NOTE Don't forget to close the stream. - +/** + * See NOTEs for GenC in StdIn. + * + * NOTE Don't attempt to create a FileInputStream directly. Use FileInputStream.open instead. + * + * NOTE Don't forget to close the stream. + */ @library object FileInputStream { @@ -87,6 +88,49 @@ case class FileInputStream private (var filename: Option[String], var consumed: filename.isDefined } + /** + * Read the next byte of data from the stream. + * + * NOTE on failure (i.e. EOF), 0 is returned + */ + @library + def readByte()(implicit state: State): Byte = { + require(isOpen) + tryReadByte() getOrElse 0 + } + + /** + * Attempt to read the next byte of data. + */ + @library + def tryReadByte()(implicit state: State): Option[Byte] = { + require(isOpen) + + var valid = true + + // Similar to tryReadInt, but here we have to use %c to read a byte + // (which assumes CHAR_BITS == 8) because SCNi8 will read char '0' to '9' + // and not the "raw" data. + @cCode.function(code = """ + |int8_t __FUNCTION__(FILE** this, void** unused, bool* valid) { + | int8_t x; + | *valid = fscanf(*this, "%c", &x) == 1; + | return x; + |} + """, + includes = "inttypes.h" + ) + def impl(): Byte = { + state.seed += 1 + val (check, value) = nativeReadByte(state.seed) + valid = check + value + } + + val res = impl() + if (valid) Some(res) else None() + } + /** * Read the next signed decimal integer * @@ -105,7 +149,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: def tryReadInt()(implicit state: State): Option[Int] = { require(isOpen) - var valid = true; + var valid = true // Because this is a nested function, contexts variables are passes by reference. @cCode.function(code = """ @@ -129,6 +173,27 @@ case class FileInputStream private (var filename: Option[String], var consumed: } // Implementation detail + @library + @extern + @cCode.drop + private def nativeReadByte(seed: BigInt): (Boolean, Byte) = { + val in = new java.io.FileInputStream(filename.get) + + // Skip what was already consumed by previous reads + assert(in.skip(consumed.toLong) == consumed.toLong) // Yes, skip might not skip the requested number of bytes... + + val b = Array[Byte](0) + val read = in.read(b) + + in.close() + + if (read != 1) (false, 0) + else { + consumed += read + (true, b(0)) + } + } + @library @extern @cCode.drop diff --git a/library/leon/io/FileOutputStream.scala b/library/leon/io/FileOutputStream.scala index 79a3872cb..9a7b0a95d 100644 --- a/library/leon/io/FileOutputStream.scala +++ b/library/leon/io/FileOutputStream.scala @@ -86,6 +86,36 @@ case class FileOutputStream private (var filename: Option[String]) { // We assume the stream to be opened if and only if the filename is defined. def isOpen(): Boolean = filename.isDefined + /** + * Append a byte to the stream and return `true` on success. + * + * NOTE The stream must be opened first. + * + * NOTE This is the symmetric function to FileInputStream.readByte; + * i.e. the same restrictions applies to GenC. + */ + @extern + @cCode.function(code = + """ + |bool __FUNCTION__(FILE* this, int8_t x) { + | return fprintf(this, "%c", x) >= 0; + |} + """, + includes = "inttypes.h" + ) + def write(x: Byte): Boolean = { + require(isOpen) + try { + val out = new java.io.FileOutputStream(filename.get, true) + val b = Array[Byte](x) + out.write(b, 0, 1) + out.close() + true + } catch { + case _: Throwable => false + } + } + /** * Append an integer to the stream and return `true` on success. * diff --git a/src/test/resources/regression/genc/valid/ByteEchoFromFile.scala b/src/test/resources/regression/genc/valid/ByteEchoFromFile.scala new file mode 100644 index 000000000..e56272026 --- /dev/null +++ b/src/test/resources/regression/genc/valid/ByteEchoFromFile.scala @@ -0,0 +1,40 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ +import leon.io._ + +object ByteEchoFromFile { + + def _main(): Int = { + implicit val state = newState + + val in = FileInputStream.open("CODING_GUIDELINES.md") + val out = FileOutputStream.open("brain.md") + + val res = + if (in.isOpen && out.isOpen) { + var b = in.tryReadByte() + + (while (b.isDefined) { + out.write(b.get) + StdOut.print(b.get) + b = in.tryReadByte() + }) invariant { + in.isOpen && out.isOpen + } + + 0 + } else 1 + + in.close() + out.close() + + res + } // no ensuring here because the opening of the files might fail. + + @extern + def main(args: Array[String]) = _main() + +} + From c592017db9ba97b010405d198b2985699294f25b Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 6 Dec 2016 14:50:13 +0100 Subject: [PATCH 37/77] Add support for more forms of Pattern Matching in GenC Namely, LetTuple & LetPattern constructs --- .../leon/genc/phases/Scala2IRPhase.scala | 10 ++-- .../genc/valid/PatternMatching5.scala | 56 +++++++++++++++++++ .../genc/valid/PatternMatching6.scala | 33 +++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/PatternMatching5.scala create mode 100644 src/test/resources/regression/genc/valid/PatternMatching6.scala diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index c33111bd3..87477f63f 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -241,8 +241,8 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case Block(Nil, v: Variable) => (v, None, env) case Block(init, v: Variable) => (v, Some(rec(Block(init.init, init.last))), env) - case fi @ FunctionInvocation(_, _) => withTmp(scrutinee0.getType, fi, env) - case cc @ CaseClass(_, _) => withTmp(scrutinee0.getType, cc, env) + case _: FunctionInvocation | _: CaseClass | _: LetVar | _: Let | _: Tuple => + withTmp(scrutinee0.getType, scrutinee0, env) case e => internalError(s"scrutinee = $e of type ${e.getClass} is not supported") } @@ -308,10 +308,12 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe CIR.True case CaseClassPattern(b, ct, subs) => - val cast = AsInstanceOf(scrutinee, ct) + val (checkType, cast) = + if (scrutinee.getType == ct) CIR.True -> scrutinee // Omit useless type checks & casts + else rec(IsInstanceOf(scrutinee, ct)) -> AsInstanceOf(scrutinee, ct) + update(b, cast) - val checkType = rec(IsInstanceOf(scrutinee, ct)) // Use the classDef fields to have the original identifiers! val checkSubs = (ct.classDef.fields zip subs) map { case (field, sub) => ccRec(sub, CaseClassSelector(ct, cast, field.id)) diff --git a/src/test/resources/regression/genc/valid/PatternMatching5.scala b/src/test/resources/regression/genc/valid/PatternMatching5.scala new file mode 100644 index 000000000..951b095ce --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching5.scala @@ -0,0 +1,56 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object PatternMatching5 { + + @inline + def foo(): (Int, Int) = { + var index = 0 + var value: Int = Byte.MinValue + + (value, index) + } + + def bar { + val (a, b) = foo() + } + + @inline + def fun(): (Array[Int], Int) = { + val a = Array(0, 1) + var idx = 0 + (a, idx) + } + + def gun { + val (dict, index) = fun() + } + + def simple1: Int = { + val (one, two) = (1, 2) + one + two + } + + def simple2(t: (Byte, Byte)): Int = { + val (x, y) = t + x + y + } + + def _main(): Int = { + // bar & gun test compilation only, not execution + bar + gun + + val test1 = simple1 - 3 + val test2 = simple2((42, 58)) - 100 + + test1 + test2 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/valid/PatternMatching6.scala b/src/test/resources/regression/genc/valid/PatternMatching6.scala new file mode 100644 index 000000000..62cc62d5a --- /dev/null +++ b/src/test/resources/regression/genc/valid/PatternMatching6.scala @@ -0,0 +1,33 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object PatternMatching6 { + + case class Pair(x: Int, y: Int) + + def max(p: Pair): Int = { + val Pair(x, y) = p + if (x >= y) x else y + } + + def min(p: Pair): Int = { + val Pair(x, y) = p + if (x <= y) x else y + } + + def sum(p: Pair): Int = { + min(p) + max(p) + } + + def _main(): Int = { + val p = Pair(-1, 1) + sum(p) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + From f71f3f19d47d9e3c69d2eee366264cdf75070d87 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 6 Dec 2016 15:41:15 +0100 Subject: [PATCH 38/77] Fix type probing on enumerations --- .../scala/leon/genc/phases/IR2CPhase.scala | 28 +++++++++++------- .../regression/genc/valid/Inheritance10.scala | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/Inheritance10.scala diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala index 93024d720..8abd2055e 100644 --- a/src/main/scala/leon/genc/phases/IR2CPhase.scala +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -190,7 +190,7 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { case While(cond, body) => C.While(rec(cond), C.buildBlock(rec(body))) // Find out if we can actually handle IsInstanceOf. - case IsA(_, ClassType(cd)) if cd.parent.isEmpty => C.True // Since it has typecheck, it can only be true. + case IsA(_, ClassType(cd)) if cd.parent.isEmpty => C.True // Since it has typechecked, it can only be true. // Currently, classes are tagged with a unique ID per class hierarchy, but // without any kind of ordering. This means we cannot have test for membership @@ -199,10 +199,13 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { case IsA(_, ClassType(cd)) if cd.isAbstract => internalError("Cannot handle membership test with abstract types for now") - case IsA(expr, ct) => + case IsA(expr0, ct) => val tag = getEnumLiteralFor(ct.clazz) - val tagAccess = C.FieldAccess(rec(expr), TaggedUnion.tag) - C.BinOp(Equals, tagAccess, tag) + val expr = + if (isEnumeration(ct.clazz)) rec(expr0) // It's an enum, therefor no field to access + else C.FieldAccess(rec(expr0), TaggedUnion.tag) + + C.BinOp(Equals, expr, tag) case AsA(expr, ClassType(cd)) if cd.parent.isEmpty => rec(expr) // no casting, actually @@ -276,17 +279,20 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { private def constructObject(cd: ClassDef, args: Seq[Expr]): C.Expr = { require(!cd.isAbstract) - val leaves = cd.getHierarchyLeaves // only leaves have fields - if (leaves exists { cd => cd.fields.nonEmpty }) { - if (cd.parent.isEmpty) simpleConstruction(cd, args) - else hierarchyConstruction(cd, args) - } else enumerationConstruction(cd, args) + // Mind the order of the cases w.r.t. the description above: they don't match. + if (isEnumeration(cd)) enumerationConstruction(cd, args) // case n° 3 + else if (cd.parent.isEmpty) simpleConstruction(cd, args) // case n° 1 + else hierarchyConstruction(cd, args) // case n° 2 } private def convertClass(cd: ClassDef): C.Type = { + if (isEnumeration(cd)) getEnumFor(cd.hierarchyTop) + else getStructFor(cd) + } + + private def isEnumeration(cd: ClassDef): Boolean = { val leaves = cd.getHierarchyLeaves - if (leaves exists { cd => cd.fields.nonEmpty }) getStructFor(cd) - else getEnumFor(cd.hierarchyTop) + leaves forall { cd => cd.fields.isEmpty } } private val markedAsEmpty = MutableSet[ClassDef]() diff --git a/src/test/resources/regression/genc/valid/Inheritance10.scala b/src/test/resources/regression/genc/valid/Inheritance10.scala new file mode 100644 index 000000000..d79598f1f --- /dev/null +++ b/src/test/resources/regression/genc/valid/Inheritance10.scala @@ -0,0 +1,29 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object Inheritance10 { + + sealed abstract class Status + case class Success() extends Status + case class OpenError() extends Status + case class EncodeError() extends Status + case class DecodeError() extends Status + + def statusCode(s: Status): Int = s match { + case Success() => 0 + case OpenError() => 1 + case EncodeError() => 2 + case DecodeError() => 3 + } + + def _main(): Int = { + statusCode(Success()) + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() + +} + From 261bcb9e930f07980557f5f62403afd2613c58e8 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 6 Dec 2016 16:26:10 +0100 Subject: [PATCH 39/77] Fix mismatch in global id counter with FieldAssignment --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 87477f63f..e81a8666c 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -483,7 +483,17 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case Assignment(id, expr) => CIR.Assign(buildBinding(id), rec(expr)) - case FieldAssignment(obj, fieldId, expr) => + case FieldAssignment(obj, fieldId0, expr) => + // The fieldId might actually not be the correct one; + // it's global counter might differ from the one in the class definition. + val fieldId = obj.getType match { + case ct: ClassType => + val fields = ct.classDef.fields + val optFieldId = fields collectFirst { case field if field.id.name == fieldId0.name => field.id } + optFieldId getOrElse { internalError(s"No corresponding field for $fieldId0 in class $ct") } + + case typ => internalError(s"Unexpected type $typ. Only class type are expected to update fields") + } CIR.Assign(CIR.FieldAccess(rec(obj), rec(fieldId)), rec(expr)) case LetDef(_, body) => From 49ae29842710273da9a6479dca993fb9dd2fe765 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 6 Dec 2016 16:56:36 +0100 Subject: [PATCH 40/77] When building an array using call by name, always freshen ids --- .../leon/genc/phases/Scala2IRPhase.scala | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index e81a8666c..d759f862a 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -7,7 +7,7 @@ package phases import purescala.Common._ import purescala.Definitions._ import purescala.Expressions._ -import purescala.{ ExprOps, TypeOps } +import purescala.{ ExprOps, TypeOps, TreeTransformer } import purescala.Types._ import xlang.Expressions._ @@ -200,6 +200,31 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe private def castNotSupported(ct: ClassType): Boolean = ct.classDef.isAbstract && ct.classDef.hasParent + // Freshen the identifiers of new variable definitions and keeps free variables as is. + private def freshen(e: Expr): Expr = { + val freshIds = MutableMap[Identifier, Identifier]() + + val transformer = new TreeTransformer { + override def transform(id: Identifier) = freshIds.getOrElse(id, id) + + override def transform(e: Expr)(implicit bindings: Map[Identifier, Identifier]): Expr = e match { + case Let(x, v, b) => + val y = x.freshen + freshIds += x -> y + super.transform(Let(y, v, b)) + + case LetVar(x, v, b) => + val y = x.freshen + freshIds += x -> y + super.transform(LetVar(y, v, b)) + + case e => super.transform(e) + } + } + + transformer.transform(e)(Map.empty) + } + /**************************************************************************************************** @@ -536,12 +561,11 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case array @ NonemptyArray(empty, Some((value0, length0))) if empty.isEmpty => val arrayType = rec(array.getType).asInstanceOf[CIR.ArrayType] - val value = rec(value0) // Convert to VLA or normal array val alloc = rec(length0) match { case CIR.Lit(L.Int32Lit(length)) => - val values = (0 until length) map { _ => value } // the same expression, != same runtime value + val values = (0 until length) map { _ => rec(freshen(value0)) } // the same expression, != same runtime value CIR.ArrayAllocStatic(arrayType, length, values) case length => @@ -550,6 +574,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe warning(s"VLAs should be avoid according to MISRA C rules", array.getPos) + val value = rec(value0) CIR.ArrayAllocVLA(arrayType, length, value) } From a539b6a32cd85c777c1fd8b6e44b7e0dbd0f549e Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 18:56:40 +0100 Subject: [PATCH 41/77] Fix Referentiator and Normalisator relationship --- src/main/scala/leon/genc/CAST.scala | 2 +- .../scala/leon/genc/ir/Referentiator.scala | 58 +++++++++++++++---- .../regression/genc/valid/Aliasing5.scala | 35 +++++++++++ .../regression/genc/valid/Aliasing6.scala | 38 ++++++++++++ 4 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/regression/genc/valid/Aliasing5.scala create mode 100644 src/test/resources/regression/genc/valid/Aliasing6.scala diff --git a/src/main/scala/leon/genc/CAST.scala b/src/main/scala/leon/genc/CAST.scala index ff15cfa4b..2d9ccb8cf 100644 --- a/src/main/scala/leon/genc/CAST.scala +++ b/src/main/scala/leon/genc/CAST.scala @@ -100,7 +100,7 @@ object CAST { // C Abstract Syntax Tree case class Decl(id: Id, typ: Type) extends Expr case class DeclInit(id: Id, typ: Type, value: Expr) extends Expr { - require(value.isValue && !value.isReference) + require(value.isValue) } case class DeclArrayStatic(id: Id, base: Type, length: Int, values: Seq[Expr]) extends Expr { diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 2acc42e08..c5968536e 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -42,8 +42,18 @@ import collection.mutable.{ Stack => MutableStack, Set => MutableSet } final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) with MiniReporter { import from._ - type Env = Map[ValDef, to.ValDef] - val Ø = Map[ValDef, to.ValDef]() + case class Env(vds: Map[ValDef, to.ValDef], mutableParams: Set[to.ValDef]) { + def apply(vd: ValDef) = vds(vd) + + def +(mapping: (ValDef, to.ValDef)) = copy(vds = vds + mapping) + + def withParams(mapping: Map[ValDef, to.ValDef]): Env = { + val refs = mapping.values.toSet filter { _.isReference } + copy(vds = vds ++ mapping, mutableParams = refs) + } + } + + val Ø = Env(Map.empty, Set.empty) // Keep track of the block we are traversing right now private val blocks = MutableStack[Block]() @@ -54,6 +64,11 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi // Registry of ValDef declared using Decl and which are references. private val knownDeclRef = MutableSet[to.ValDef]() + private def isKnownDeclRef(vd: ValDef)(implicit env: Env) = { + val to = env.vds(vd) + knownDeclRef(to) || env.mutableParams(to) + } + override def recImpl(fd: FunDef)(implicit env: Env): to.FunDef = { val id = fd.id @@ -75,7 +90,7 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi } // Build the new environment: from CIR to RIR - val newEnv = env ++ ((fd.ctx ++ fd.params) zip (ctx ++ params)) + val newEnv = env.withParams(((fd.ctx ++ fd.params) zip (ctx ++ params)).toMap) // Handle recursive functions val newer = to.FunDef(id, returnType, ctx, params, null) @@ -128,7 +143,15 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi val newEnv = env + (vd0 -> vd) to.Decl(vd) -> newEnv - case DeclInit(vd0, Binding(rhs0)) if !lookingAhead && knownDeclRef(env(rhs0)) => + case DeclInit(vd0, Binding(rhs0)) if rhs0.getType.isMutable => + // Here we patch aliases on references generated by normalisation. + val vd = toReference(rec(vd0)) + val newEnv = env + (vd0 -> vd) + val value1 = to.Binding(env(rhs0)) + val value = if (value1.getType.isReference) value1 else ref(value1) + to.DeclInit(vd, value) -> newEnv + + case DeclInit(vd0, Binding(rhs0)) if !lookingAhead && isKnownDeclRef(rhs0) => // Here we patch aliases on references generated by normalisation. val vd = toReference(rec(vd0)) val newEnv = env + (vd0 -> vd) @@ -156,14 +179,29 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi to.Construct(cd, args) -> env - case Assign(Binding(vd0), rhs0) if !lookingAhead && knownDeclRef(env(vd0)) => - // Here we handle the special case that derives from declImpl. - val rhs = rec(rhs0) match { - case to.Deref(rhs) => rhs // We do *not* want this deferencing - case e => internalError(s"Unhandled case $e: ${e.getClass}") + case Assign(lhs0, rhs0) => + val lhs1 = rec(lhs0) + val rhs1 = rec(rhs0) + + // Prevent normalisation variable to code a mutable variable; keep pointers! + // + // Example of what we don't want: + // void foo(bool flag, int* ptr1, int* ptr2) { + // int* norm; + // if (flag) *norm = *ptr1; else *norm = *ptr2; + // } + // + // Instead, what we want is: + // if (flag) norm = ptr1; else norm = ptr2; + // + val (lhs, rhs) = (lhs1, rhs1) match { + case (to.Deref(lhs), to.Deref(rhs)) => (lhs, rhs) + case (to.Deref(lhs), rhs) if rhs.getType.isReference => internalError(s"$lhs0 = $rhs0 was translated into $lhs1 = $rhs1; $lookingAhead") + case _ => (lhs1, rhs1) } - to.Assign(to.Binding(env(vd0)), rhs) -> env + to.Assign(lhs, rhs) -> env + case e => super.recImpl(e) } diff --git a/src/test/resources/regression/genc/valid/Aliasing5.scala b/src/test/resources/regression/genc/valid/Aliasing5.scala new file mode 100644 index 000000000..3ae112f60 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing5.scala @@ -0,0 +1,35 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation.extern + +object Aliasing5 { + + case class Buffer(val array: Array[Int], var len: Int) { + require(0 <= len && len <= array.length) + + def apply(i: Int): Int = { + require(0 <= i && i < len) + array(i) + } + + def append(x: Int): Unit = { + require(len < array.length) + array(len) = x + len += 1 + } + } + + def _main(): Int = { + val b1 = Buffer(Array(0, 0, 0, 0, 0), 0) + val b2 = Buffer(Array(1, 2, 3, 0, 0), 3) + + b1.append(b2(1)) + + if (b1(0) == 2 && b1.len == 1) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + diff --git a/src/test/resources/regression/genc/valid/Aliasing6.scala b/src/test/resources/regression/genc/valid/Aliasing6.scala new file mode 100644 index 000000000..96b0298a2 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing6.scala @@ -0,0 +1,38 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object Aliasing6 { + + case class Buffer(val array: Array[Int], var len: Int) { + require(0 <= len && len <= array.length) + + def apply(i: Int): Int = { + require(0 <= i && i < len) + array(i) + } + + def append(x: Int): Unit = { + require(len < array.length) + array(len) = x + len += 1 + } + } + + @inline + def createBuffer() = Buffer(Array.fill(10)(0), 0) + + def _main(): Int = { + val b1 = createBuffer() + val b2 = Buffer(Array(1, 2, 3, 0, 0), 3) + + b1.append(b2(1)) + + if (b1(0) == 2 && b1.len == 1) 0 + else 1 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + From 584ae3b99944fad8cfc236c29736ee7333d19868 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 20:03:21 +0100 Subject: [PATCH 42/77] Fix more aliasing issues --- .../scala/leon/genc/ir/Referentiator.scala | 5 +++ .../regression/genc/valid/Aliasing7.scala | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/test/resources/regression/genc/valid/Aliasing7.scala diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index c5968536e..000647674 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -157,6 +157,11 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi val newEnv = env + (vd0 -> vd) to.DeclInit(vd, to.Binding(env(rhs0))) -> newEnv + case DeclInit(vd0, fa @ FieldAccess(_, _)) if fa.getType.isMutable => + val vd = toReference(rec(vd0)) + val newEnv = env + (vd0 -> vd) + to.DeclInit(vd, ref(rec(fa))) -> newEnv + case DeclInit(vd0, value0) => val vd = rec(vd0) val value = rec(value0) diff --git a/src/test/resources/regression/genc/valid/Aliasing7.scala b/src/test/resources/regression/genc/valid/Aliasing7.scala new file mode 100644 index 000000000..de690d416 --- /dev/null +++ b/src/test/resources/regression/genc/valid/Aliasing7.scala @@ -0,0 +1,36 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object Aliasing7 { + + case class Mutable(var i: Int) + + case class Wutable(var x: Int, val m: Mutable) + + def inc(x: Int): Int = { + require(x < 1000) + x + 1 + } + + def foo(x: Int, m: Mutable): Int = { + m.i += 1 + x + m.i + } + + def _main(): Int = { + val w = Wutable(42, Mutable(58)) + + val s = foo(inc(w.x), w.m) + + if (s == 102) { + if (w.m.i == 59) 0 + else 1 + } else 2 + } ensuring { _ == 0 } + + @extern + def main(args: Array[String]): Unit = _main() +} + From 782c9b93b80f13d7741cd1a71950b1aeac33417d Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 20:05:00 +0100 Subject: [PATCH 43/77] Fix incorrect assumption in Expr.getType --- src/main/scala/leon/genc/ir/IR.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index f57f75ef8..c1c965797 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -164,7 +164,13 @@ private[genc] sealed trait IR { ir => case App(fd, _, _) => fd.returnType case Construct(cd, _) => ClassType(cd) case ArrayInit(alloc) => alloc.typ - case FieldAccess(objekt, fieldId) => objekt.getType.asInstanceOf[ClassType].clazz getFieldType fieldId + case FieldAccess(objekt, fieldId) => + val ct = objekt.getType match { + case ct: ClassType => ct + case ReferenceType(ct: ClassType) => ct + case _ => ??? + } + ct.clazz getFieldType fieldId case ArrayAccess(array, _) => array.getType.asInstanceOf[ArrayType].base case ArrayLength(_) => PrimitiveType(Int32Type) case Assign(_, _) => NoType From bbf6d6e7b2653afb4670ac283142b81c6fc778e7 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 20:08:46 +0100 Subject: [PATCH 44/77] Significantly optimise processing initialisation of arrays of zeros in GenC --- src/main/scala/leon/genc/ir/IR.scala | 27 ++++++++++++------- src/main/scala/leon/genc/ir/IRPrinter.scala | 10 +++++-- src/main/scala/leon/genc/ir/Normaliser.scala | 9 +++++-- src/main/scala/leon/genc/ir/Transformer.scala | 7 +++-- src/main/scala/leon/genc/ir/Visitor.scala | 5 +++- .../scala/leon/genc/phases/IR2CPhase.scala | 15 +++++++++-- .../leon/genc/phases/Scala2IRPhase.scala | 8 ++++-- 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/main/scala/leon/genc/ir/IR.scala b/src/main/scala/leon/genc/ir/IR.scala index c1c965797..ca10df2ea 100644 --- a/src/main/scala/leon/genc/ir/IR.scala +++ b/src/main/scala/leon/genc/ir/IR.scala @@ -124,17 +124,26 @@ private[genc] sealed trait IR { ir => val typ: ArrayType } - def check(t: Tree)(valid: Boolean) { if (!valid) sys.error(s"Invalid $t") } + // For optimisation of ArrayAllocStatic: avoid processing useless bits in GenC to speed up things for big arrays. + case object Zero // Allocate an array with a compile-time size - case class ArrayAllocStatic(typ: ArrayType, length: Int, values: Seq[Expr]) extends ArrayAlloc { - check(this)( - // The type of the values should match the type of the array elements - (values forall { _.getType <= typ.base }) && - // The number of values should match the array size - (length == values.length) && - // And empty arrays are forbidden - (length > 0) + case class ArrayAllocStatic(typ: ArrayType, length: Int, values: Either[Zero.type, Seq[Expr]]) extends ArrayAlloc { + require( + values match { + case Left(z) => + // No empty array + (length > 0) && + typ.base.isIntegral + + case Right(values) => + // The type of the values should match the type of the array elements + (values forall { _.getType <= typ.base }) && + // The number of values should match the array size + (length == values.length) && + // And empty arrays are forbidden + (length > 0) + } ) } diff --git a/src/main/scala/leon/genc/ir/IRPrinter.scala b/src/main/scala/leon/genc/ir/IRPrinter.scala index 98521d636..ab8db638c 100644 --- a/src/main/scala/leon/genc/ir/IRPrinter.scala +++ b/src/main/scala/leon/genc/ir/IRPrinter.scala @@ -72,8 +72,14 @@ final class IRPrinter[S <: IR](val ir: S) { private def rec(alloc: ArrayAlloc)(implicit ptx: Context): String = { (alloc: @unchecked) match { - case ArrayAllocStatic(arrayType, length, values) => "Array[" + rec(arrayType.base) + "](" + (values map rec mkString ", ") + ")" - case ArrayAllocVLA(arrayType, length, valueInit) => "Array[" + rec(arrayType.base) + "].fill(" + rec(length) + ")(" + rec(valueInit) + ")" + case ArrayAllocStatic(arrayType, length, Right(values)) => + "Array[" + rec(arrayType.base) + "](" + (values map rec mkString ", ") + ")" + + case ArrayAllocStatic(arrayType, length, Left(z)) => + "Array[" + rec(arrayType.base) + "]( 0's " + length + " times )" + + case ArrayAllocVLA(arrayType, length, valueInit) => + "Array[" + rec(arrayType.base) + "].fill(" + rec(length) + ")(" + rec(valueInit) + ")" } } diff --git a/src/main/scala/leon/genc/ir/Normaliser.scala b/src/main/scala/leon/genc/ir/Normaliser.scala index 8d34399b4..1daaf4135 100644 --- a/src/main/scala/leon/genc/ir/Normaliser.scala +++ b/src/main/scala/leon/genc/ir/Normaliser.scala @@ -39,12 +39,17 @@ final class Normaliser(val ctx: LeonContext) extends Transformer(CIR, NIR) with val vd = rec(vd0) val (preAlloc, alloc) = alloc0 match { - case ArrayAllocStatic(typ, length, values0) => + case ArrayAllocStatic(typ, length, Right(values0)) => val (preValues, values) = flattenArgs(values0) - val alloc = to.ArrayAllocStatic(recAT(typ), length, values) + val alloc = to.ArrayAllocStatic(recAT(typ), length, Right(values)) preValues -> alloc + case ArrayAllocStatic(typ, length, Left(_)) => + val alloc = to.ArrayAllocStatic(recAT(typ), length, Left(to.Zero)) + + Seq.empty -> alloc + case ArrayAllocVLA(typ, length0, valueInit0) => // Here it's fine to do two independent normalisations because there will be a // sequence point between the length and the value in the C code anyway. diff --git a/src/main/scala/leon/genc/ir/Transformer.scala b/src/main/scala/leon/genc/ir/Transformer.scala index 91934edaf..06b8c8d6f 100644 --- a/src/main/scala/leon/genc/ir/Transformer.scala +++ b/src/main/scala/leon/genc/ir/Transformer.scala @@ -93,8 +93,11 @@ abstract class Transformer[From <: IR, To <: IR](final val from: From, final val protected def rec(vd: ValDef)(implicit env: Env): to.ValDef = to.ValDef(vd.id, rec(vd.typ), vd.isVar) protected def rec(alloc: ArrayAlloc)(implicit env: Env): to.ArrayAlloc = (alloc: @unchecked) match { - case ArrayAllocStatic(ArrayType(base), length, values) => - to.ArrayAllocStatic(to.ArrayType(rec(base)), length, values map rec) + case ArrayAllocStatic(ArrayType(base), length, Right(values)) => + to.ArrayAllocStatic(to.ArrayType(rec(base)), length, Right(values map rec)) + + case ArrayAllocStatic(ArrayType(base), length, Left(_)) => + to.ArrayAllocStatic(to.ArrayType(rec(base)), length, Left(to.Zero)) case ArrayAllocVLA(ArrayType(base), length, valueInit) => to.ArrayAllocVLA(to.ArrayType(rec(base)), rec(length), rec(valueInit)) diff --git a/src/main/scala/leon/genc/ir/Visitor.scala b/src/main/scala/leon/genc/ir/Visitor.scala index 9bb3a3789..63f4cfcb0 100644 --- a/src/main/scala/leon/genc/ir/Visitor.scala +++ b/src/main/scala/leon/genc/ir/Visitor.scala @@ -76,7 +76,10 @@ abstract class Visitor[S <: IR](final val ir: S) { (alloc: @unchecked) match { case ArrayAllocStatic(arrayType, length, values) => rec(arrayType) - values foreach rec + values match { + case Right(values) => values foreach rec + case _ => + } case ArrayAllocVLA(arrayType, length, valueInit) => rec(arrayType) diff --git a/src/main/scala/leon/genc/phases/IR2CPhase.scala b/src/main/scala/leon/genc/phases/IR2CPhase.scala index 8abd2055e..683af0ba9 100644 --- a/src/main/scala/leon/genc/phases/IR2CPhase.scala +++ b/src/main/scala/leon/genc/phases/IR2CPhase.scala @@ -130,9 +130,20 @@ private class IR2CImpl(val ctx: LeonContext) extends MiniReporter { case Decl(vd) => C.Decl(rec(vd.id), rec(vd.getType)) - case DeclInit(vd, ArrayInit(ArrayAllocStatic(arrayType, length, values))) => + case DeclInit(vd, ArrayInit(ArrayAllocStatic(arrayType, length, values0))) => val bufferId = C.FreshId("buffer") - val bufferDecl = C.DeclArrayStatic(bufferId, rec(arrayType.base), length, values map rec) + val values = values0 match { + case Right(values0) => values0 map rec + case Left(_) => + // By default, 0-initialisation using only zero value + val z = arrayType.base match { + case PrimitiveType(Int8Type) => Int8Lit(0) + case PrimitiveType(Int32Type) => Int32Lit(0) + case _ => internalError(s"Unexpected integral type $arrayType") + } + Seq(C.Lit(z)) + } + val bufferDecl = C.DeclArrayStatic(bufferId, rec(arrayType.base), length, values) val data = C.Binding(bufferId) val len = C.Lit(Int32Lit(length)) val array = array2Struct(arrayType) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index d759f862a..20e99aed7 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -565,7 +565,11 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe // Convert to VLA or normal array val alloc = rec(length0) match { case CIR.Lit(L.Int32Lit(length)) => - val values = (0 until length) map { _ => rec(freshen(value0)) } // the same expression, != same runtime value + // Optimisation for zero: don't generate values at all to speed up processing within GenC. + val values = value0 match { + case IntLiteral(0) | ByteLiteral(0) => Left(CIR.Zero) + case value0 => Right((0 until length) map { _ => rec(freshen(value0)) }) // the same expression, != same runtime value + } CIR.ArrayAllocStatic(arrayType, length, values) case length => @@ -594,7 +598,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe if (values exists { _.getType != arrayType.base }) fatalError("Heterogenous arrays", array.getPos) - val alloc = CIR.ArrayAllocStatic(arrayType, values.length, values) + val alloc = CIR.ArrayAllocStatic(arrayType, values.length, Right(values)) CIR.ArrayInit(alloc) case IfExpr(cond, thenn, NoTree(_)) => CIR.If(rec(cond), rec(thenn)) From b00e85f06f867ec11df117da30207684b3be1fef Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 20:10:57 +0100 Subject: [PATCH 45/77] Fix some typo --- src/main/scala/leon/genc/ir/Referentiator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 000647674..34e13ad25 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -188,7 +188,7 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi val lhs1 = rec(lhs0) val rhs1 = rec(rhs0) - // Prevent normalisation variable to code a mutable variable; keep pointers! + // Prevent normalisation variable to copy a mutable variable; keep pointers! // // Example of what we don't want: // void foo(bool flag, int* ptr1, int* ptr2) { From b2a45f55bcbc24c2ccf95727c695ad27ce445b74 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 20:31:00 +0100 Subject: [PATCH 46/77] Make C compilers not emit warnings with ^, |, and & by using parentheses --- src/main/scala/leon/genc/CPrinter.scala | 2 +- src/main/scala/leon/genc/ir/Operators.scala | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala index a27c2ae67..d281e6acb 100644 --- a/src/main/scala/leon/genc/CPrinter.scala +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -227,7 +227,7 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { private def requiresParentheses(current: Tree, previous: Option[Tree]): Boolean = (current, previous) match { case (_, None) => false case (_, Some(_: DeclInit | _: Call | _: ArrayAccess | _: If | _: IfElse | _: While | _: Return | _: Assign)) => false - case (Operator(precedence1), Some(Operator(precedence2))) if precedence1 <= precedence2 => false + case (Operator(precedence1), Some(Operator(precedence2))) if precedence1 < precedence2 => false case (_, _) => true } diff --git a/src/main/scala/leon/genc/ir/Operators.scala b/src/main/scala/leon/genc/ir/Operators.scala index 01824c3ba..03a97912a 100644 --- a/src/main/scala/leon/genc/ir/Operators.scala +++ b/src/main/scala/leon/genc/ir/Operators.scala @@ -60,9 +60,11 @@ private[genc] object Operators { case object Or extends BinaryOperator("||", 12) with Logical case object BNot extends UnaryOperator("~", 2) with Integral - case object BAnd extends BinaryOperator("&", 8) with Integral - case object BXor extends BinaryOperator("^", 9) with Integral - case object BOr extends BinaryOperator("|", 10) with Integral + case object BAnd extends BinaryOperator("&", 8) with Integral // NOTE to avoid warning from compilers, + case object BXor extends BinaryOperator("^", 8) with Integral // we make sure to add parenthesis + case object BOr extends BinaryOperator("|", 8) with Integral // for those three operators... + // ... even though it's safe no to add parenthesis. + case object BLeftShift extends BinaryOperator("<<", 5) with Integral case object BRightShift extends BinaryOperator(">>", 5) with Integral From 4c99fcc3db437f84f14dae703e738860855e4bc9 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 15:33:02 +0100 Subject: [PATCH 47/77] Properly reject functions returning arrays --- .../leon/genc/phases/ComputeFunCtxPhase.scala | 2 +- .../leon/genc/phases/Scala2IRPhase.scala | 9 ++++++++- .../regression/genc/invalid/ReturnArray.scala | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/regression/genc/invalid/ReturnArray.scala diff --git a/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala index b38839dd3..be32266f0 100644 --- a/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala +++ b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala @@ -63,7 +63,7 @@ private[genc] object ComputeFunCtxPhase extends TimedLeonPhase[Dependencies, Fun fds foreach { fd => processFunction(fd, ctx) } rec(rest, ctx) - // Because technically a function could be defined a block which is itself an argument, + // Because technically a function could be defined in a block which is itself an argument, // we recurse on arguments as well! // This also includes Terminal-like expression and therefore stop recursion when needed. case Operator(args, _) => args foreach { arg => rec(arg, ctx) } diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 20e99aed7..b9298c4f8 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -23,6 +23,11 @@ import scala.collection.mutable.{ Map => MutableMap } /* * This phase takes a set of definitions (the Dependencies) and the fonction context database (FunCtxDB) * and produces an equivalent program expressed in the intermediate representation without generic types (CIR). + * + * NOTE This phase also rejects fragment of Scala that are not supported by GenC, such as returning + * or copying arrays, constructing a case class with mutable fields from function arguments, + * the >> operator, some forms of membership tests, the unapply pattern matching construct, + * and more. */ private[genc] object Scala2IRPhase extends TimedLeonPhase[(Dependencies, FunCtxDB), CIR.ProgDef] { val name = "Scala to IR converter" @@ -362,7 +367,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe buildBinOp(scrutinee, O.Equals, lit)(pat.getPos) case UnapplyPattern(bind, unapply, subs) => - fatalError(s"Unapply Pattern, a.k.a. Extractor Objects", pat.getPos) + fatalError(s"Unapply Pattern, a.k.a. Extractor Objects, is not supported", pat.getPos) } val cond = ccRec(caze.pattern, initialScrutinee) @@ -397,6 +402,8 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe val ctx = ctxDB(tfd.fd) map { c => convertVarInfoToArg(c)(tm1) } val returnType = rec(tfd.returnType)(tm1) + if (returnType.containsArray) + fatalError("Returning arrays from function is not supported", tfd.getPos) // Build a partial function without body in order to support recursive functions val fun = CIR.FunDef(id, returnType, ctx, params, null) diff --git a/src/test/resources/regression/genc/invalid/ReturnArray.scala b/src/test/resources/regression/genc/invalid/ReturnArray.scala new file mode 100644 index 000000000..55da6446e --- /dev/null +++ b/src/test/resources/regression/genc/invalid/ReturnArray.scala @@ -0,0 +1,19 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object ReturnArray { + + def foo: Array[Int] = Array(0, 1, 2) + + def _main(): Int = { + val a = foo + 0 + } + + @extern + def main(args: Array[String]) : Unit = _main() + +} + From 739a1c73abe4bade8bcd83ca3ac6d86ec76956aa Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 15:53:00 +0100 Subject: [PATCH 48/77] Update GenC tests Make expected errors more obvious in tests --- .../regression/genc/invalid/AbsFun.scala | 65 ------------------- .../regression/genc/invalid/ArrayCopy.scala | 16 +++++ 2 files changed, 16 insertions(+), 65 deletions(-) delete mode 100644 src/test/resources/regression/genc/invalid/AbsFun.scala create mode 100644 src/test/resources/regression/genc/invalid/ArrayCopy.scala diff --git a/src/test/resources/regression/genc/invalid/AbsFun.scala b/src/test/resources/regression/genc/invalid/AbsFun.scala deleted file mode 100644 index a58f93fb5..000000000 --- a/src/test/resources/regression/genc/invalid/AbsFun.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -import leon.annotation._ -import leon.lang._ - -object AbsFun { - - def isPositive(a: Array[Int], size: Int) : Boolean = { - require(a.length >= 0 && size <= a.length) - rec(0, a, size) - } - - def rec(i: Int, a: Array[Int], size: Int) : Boolean = { - require(a.length >= 0 && size <= a.length && i >= 0) - - if(i >= size) true - else { - if (a(i) < 0) - false - else - rec(i + 1, a, size) - } - } - - // Returning Arrays is not supported by GenC - def abs(tab: Array[Int]): Array[Int] = { - require(tab.length >= 0) - val t = while0(Array.fill(tab.length)(0), 0, tab) - t._1 - } ensuring(res => isPositive(res, res.length)) - - - def while0(t: Array[Int], k: Int, tab: Array[Int]): (Array[Int], Int) = { - require(tab.length >= 0 && - t.length == tab.length && - k >= 0 && - k <= tab.length && - isPositive(t, k)) - - if(k < tab.length) { - val nt = if(tab(k) < 0) { - t.updated(k, -tab(k)) - } else { - t.updated(k, tab(k)) - } - while0(nt, k+1, tab) - } else { - (t, k) - } - } ensuring(res => - res._2 >= tab.length && - res._1.length == tab.length && - res._2 >= 0 && - res._2 <= tab.length && - isPositive(res._1, res._2)) - - def _main(): Unit = { - val array = Array(0, 5, 3, 10, -5) - val absArray = abs(array) - } - - @extern - def main(args: Array[String]): Unit = _main() - -} diff --git a/src/test/resources/regression/genc/invalid/ArrayCopy.scala b/src/test/resources/regression/genc/invalid/ArrayCopy.scala new file mode 100644 index 000000000..d861651f7 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/ArrayCopy.scala @@ -0,0 +1,16 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ + +object ArrayCopy { + + def _main(): Int = { + val a = Array(0, 5, 3, 10, -5) + val b = a.updated(0, 1) + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} From 02b89d8623864a224ee0b7002d0c54204dc4d62c Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 16:11:17 +0100 Subject: [PATCH 49/77] Add regression tests that should fail about aliasing --- .../invalid/ObjectHierarchyMutation1.scala | 31 +++++++++++++++++++ .../invalid/ObjectHierarchyMutation2.scala | 31 +++++++++++++++++++ .../invalid/ObjectHierarchyMutation3.scala | 29 +++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala create mode 100644 src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala create mode 100644 src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala new file mode 100644 index 000000000..610342ad3 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala @@ -0,0 +1,31 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object ObjectHierarchyMutation1 { + + case class A(var x: Int) + case class B(a1: A, a2: A) + + def updateB(b: B): Unit = { + b.a1.x = 42 + b.a2.x = 41 + } + + def updateA(a1: A, a2: A): Unit = { + updateB(B(a1, a2)) + } ensuring(_ => a1.x == 42 && a2.x == 41) + + def _main(): Int = { + val a1 = A(0) + val a2 = A(0) + updateA(a1, a2) + + a1.x - a2.x - 1 // == 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala new file mode 100644 index 000000000..7656e2fa3 --- /dev/null +++ b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala @@ -0,0 +1,31 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object ObjectHierarchyMutation2 { + + case class A(var x: Int) + case class B(a: A, y: Int) + + def updateB(b1: B, b2: B): Unit = { + b1.a.x = 42 + b2.a.x = 41 + } + + def update(a1: A, a2: A, y: Int): Unit = { + updateB(B(a2, y), B(a1, y)) + } ensuring(_ => a2.x == 42 && a1.x == 41) + + def _main(): Int = { + val a1 = A(0) + val a2 = A(0) + update(a1, a2, 0) + + a1.x - a2.x - 1 // == 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala new file mode 100644 index 000000000..d9f7beacb --- /dev/null +++ b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala @@ -0,0 +1,29 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +object ObjectHierarchyMutation3 { + + case class A(var x: Int) + case class B(a: A) + + def updateB(b: B): Unit = { + b.a.x = 42 + } + + def updateA(a: A): Unit = { + updateB(B(a)) + } ensuring(_ => a.x == 42) + + def _main(): Int = { + val a = A(0) + updateA(a) + + a.x - 42 // == 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} From 9194733ca1352029732862ad10a6ca256d1b67e1 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 17:20:28 +0100 Subject: [PATCH 50/77] Add some aliasing regression tests that should get rejected They are currently disabled because implementing a rejection phase for those is not as simple as it might seem. Similar cases can occur though pattern matching, for example. --- ...chyMutation1.scala => ObjectHierarchyMutation1.scala.disabled} | 0 ...chyMutation2.scala => ObjectHierarchyMutation2.scala.disabled} | 0 ...chyMutation3.scala => ObjectHierarchyMutation3.scala.disabled} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/test/resources/regression/genc/invalid/{ObjectHierarchyMutation1.scala => ObjectHierarchyMutation1.scala.disabled} (100%) rename src/test/resources/regression/genc/invalid/{ObjectHierarchyMutation2.scala => ObjectHierarchyMutation2.scala.disabled} (100%) rename src/test/resources/regression/genc/invalid/{ObjectHierarchyMutation3.scala => ObjectHierarchyMutation3.scala.disabled} (100%) diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala.disabled similarity index 100% rename from src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala rename to src/test/resources/regression/genc/invalid/ObjectHierarchyMutation1.scala.disabled diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala.disabled similarity index 100% rename from src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala rename to src/test/resources/regression/genc/invalid/ObjectHierarchyMutation2.scala.disabled diff --git a/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala b/src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala.disabled similarity index 100% rename from src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala rename to src/test/resources/regression/genc/invalid/ObjectHierarchyMutation3.scala.disabled From 9b4a33dc61fd3782a85f572eb8fe357064ece703 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 17:41:09 +0100 Subject: [PATCH 51/77] Emit warnings on recursive functions Let the user know this is not 100% MISRA compliant. With stack size analysis this issue could be solved. --- .../scala/leon/genc/phases/ComputeDependenciesPhase.scala | 4 ++-- src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala | 2 +- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 7 ++++++- src/main/scala/leon/genc/phases/package.scala | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala b/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala index b028f9ee3..027a3c3e7 100644 --- a/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala +++ b/src/main/scala/leon/genc/phases/ComputeDependenciesPhase.scala @@ -112,7 +112,7 @@ private[genc] object ComputeDependenciesPhase extends TimedLeonPhase[(Program, D // Keep only the top level functions val deps = allDeps filterNot isNestedFun - deps + Dependencies(prog, deps) } } @@ -124,7 +124,7 @@ private final class ComputeDependenciesImpl(val ctx: LeonContext) extends MiniRe private val dependencies = MutableSet[Definition]() // Compute the dependencies of `entry`, which includes itself. - def apply(entry: Definition): Dependencies = { + def apply(entry: Definition): Set[Definition] = { entry match { case e: FunDef => traverse(e) case _ => internalError("unexpected type of entry point: ${entry.getClass}") diff --git a/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala index be32266f0..e31a41886 100644 --- a/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala +++ b/src/main/scala/leon/genc/phases/ComputeFunCtxPhase.scala @@ -73,7 +73,7 @@ private[genc] object ComputeFunCtxPhase extends TimedLeonPhase[Dependencies, Fun // Process every top level function to register function contextes for their inner functions; // Register those top level functions as well - val topLevelFuns: Set[FunDef] = deps collect { case fd: FunDef => fd } + val topLevelFuns: Set[FunDef] = deps.deps collect { case fd: FunDef => fd } val Ø = Seq[VarInfo]() topLevelFuns foreach { fd => processFunction(fd, Ø) } diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index b9298c4f8..882ea38ab 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -40,7 +40,7 @@ private[genc] object Scala2IRPhase extends TimedLeonPhase[(Dependencies, FunCtxD import reporter._ val (deps, ctxDB) = input - val entryPoint = getEntryPoint(deps) + val entryPoint = getEntryPoint(deps.deps) val impl = new S2IRImpl(ctx, ctxDB, deps) val ir = CIR.ProgDef(impl(entryPoint)) @@ -389,6 +389,11 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe private def rec(tfd: TypedFunDef)(implicit tm0: TypeMapping): CIR.FunDef = funCache get tfd -> tm0 getOrElse { val id = buildId(tfd) + // Warn user about recusrivity: this is not MISRA complient unless it is very tightly controlled. + // NOTE this check is done after VC are removed. + if (tfd.fd.isRecursive(deps.prog)) + warning(s"MISRA rules state that recursive functions should be very tightly controlled; ${tfd.id} is recursive") + // We have to manually specify tm1 from now on to avoid using tm0. We mark tm1 as // implicit as well to generate ambiguity at compile time to avoid forgetting a call site. implicit val tm1 = tm0 ++ tfd.typesMap diff --git a/src/main/scala/leon/genc/phases/package.scala b/src/main/scala/leon/genc/phases/package.scala index 43ea116a9..0535bc32a 100644 --- a/src/main/scala/leon/genc/phases/package.scala +++ b/src/main/scala/leon/genc/phases/package.scala @@ -4,7 +4,7 @@ package leon package genc import purescala.Common.{ Identifier } -import purescala.Definitions.{ Definition, FunDef, ValDef } +import purescala.Definitions.{ Definition, FunDef, ValDef, Program } import purescala.Types.{ TypeTree } /* @@ -16,7 +16,7 @@ package object phases { type FunCtxDB = Map[FunDef, Seq[VarInfo]] - type Dependencies = Set[Definition] + case class Dependencies(prog: Program, deps: Set[Definition]) } From 1b8e55f3a59bf8c63b217404c4438a84fa1f3c36 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 18:18:33 +0100 Subject: [PATCH 52/77] Enable more C optimisation by making every function static Except for the main function which should not be static --- src/main/scala/leon/genc/CPrinter.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala index d281e6acb..c6fb27e72 100644 --- a/src/main/scala/leon/genc/CPrinter.scala +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -174,9 +174,12 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { } private[genc] def pp(wt: WrapperTree)(implicit ctx: PrinterContext): Unit = wt match { - case FunSign(Fun(id, returnType, Seq(), _)) => c"$returnType $id(void)" + case StaticStorage(id) if id.name == "main" => /* Nothing */ + case StaticStorage(_) => c"static" - case FunSign(Fun(id, returnType, params, _)) => c"$returnType $id(${nary(params)})" + case FunSign(Fun(id, returnType, Seq(), _)) => c"${StaticStorage(id)} $returnType $id(void)" + + case FunSign(Fun(id, returnType, params, _)) => c"${StaticStorage(id)} $returnType $id(${nary(params)})" case FunDecl(f) => c"${FunSign(f)};" @@ -205,6 +208,7 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { /** Wrappers to distinguish how the data should be printed **/ private[genc] sealed abstract class WrapperTree + private case class StaticStorage(id: Id) extends WrapperTree private case class FunSign(f: Fun) extends WrapperTree private case class FunDecl(f: Fun) extends WrapperTree private case class TypedefDecl(td: Typedef) extends WrapperTree From ca6a2de077bb9fd68746905e9f37bb8f3f1516d3 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 13 Dec 2016 18:32:43 +0100 Subject: [PATCH 53/77] Make library functions static as well --- library/leon/io/FileInputStream.scala | 10 +++++----- library/leon/io/FileOutputStream.scala | 14 +++++++------- library/leon/io/StdIn.scala | 4 ++-- library/leon/io/StdOut.scala | 8 ++++---- library/leon/io/package.scala | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/library/leon/io/FileInputStream.scala b/library/leon/io/FileInputStream.scala index b3c464e64..748681b78 100644 --- a/library/leon/io/FileInputStream.scala +++ b/library/leon/io/FileInputStream.scala @@ -21,7 +21,7 @@ object FileInputStream { @extern @cCode.function(code = """ - |FILE* __FUNCTION__(char* filename, void* unused) { + |static FILE* __FUNCTION__(char* filename, void* unused) { | FILE* this = fopen(filename, "r"); | // this == NULL on failure | return this; @@ -56,7 +56,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: */ @cCode.function(code = """ - |bool __FUNCTION__(FILE* this, void* unused) { + |static bool __FUNCTION__(FILE* this, void* unused) { | if (this != NULL) | return fclose(this) == 0; | else @@ -78,7 +78,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: */ @cCode.function(code = """ - |bool __FUNCTION__(FILE* this) { + |static bool __FUNCTION__(FILE* this) { | return this != NULL; |} """ @@ -112,7 +112,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: // (which assumes CHAR_BITS == 8) because SCNi8 will read char '0' to '9' // and not the "raw" data. @cCode.function(code = """ - |int8_t __FUNCTION__(FILE** this, void** unused, bool* valid) { + |static int8_t __FUNCTION__(FILE** this, void** unused, bool* valid) { | int8_t x; | *valid = fscanf(*this, "%c", &x) == 1; | return x; @@ -153,7 +153,7 @@ case class FileInputStream private (var filename: Option[String], var consumed: // Because this is a nested function, contexts variables are passes by reference. @cCode.function(code = """ - |int32_t __FUNCTION__(FILE** this, void** unused, bool* valid) { + |static int32_t __FUNCTION__(FILE** this, void** unused, bool* valid) { | int32_t x; | *valid = fscanf(*this, "%"SCNd32, &x) == 1; | return x; diff --git a/library/leon/io/FileOutputStream.scala b/library/leon/io/FileOutputStream.scala index 9a7b0a95d..120e00c06 100644 --- a/library/leon/io/FileOutputStream.scala +++ b/library/leon/io/FileOutputStream.scala @@ -24,7 +24,7 @@ object FileOutputStream { @extern @cCode.function(code = """ - |FILE* __FUNCTION__(char* filename) { + |static FILE* __FUNCTION__(char* filename) { | FILE* this = fopen(filename, "w"); | // this == NULL on failure | return this; @@ -58,7 +58,7 @@ case class FileOutputStream private (var filename: Option[String]) { */ @cCode.function(code = """ - |bool __FUNCTION__(FILE* this) { + |static bool __FUNCTION__(FILE* this) { | if (this != NULL) | return fclose(this) == 0; | else @@ -78,7 +78,7 @@ case class FileOutputStream private (var filename: Option[String]) { */ @cCode.function(code = """ - |bool __FUNCTION__(FILE* this) { + |static bool __FUNCTION__(FILE* this) { | return this != NULL; |} """ @@ -97,7 +97,7 @@ case class FileOutputStream private (var filename: Option[String]) { @extern @cCode.function(code = """ - |bool __FUNCTION__(FILE* this, int8_t x) { + |static bool __FUNCTION__(FILE* this, int8_t x) { | return fprintf(this, "%c", x) >= 0; |} """, @@ -124,7 +124,7 @@ case class FileOutputStream private (var filename: Option[String]) { @extern @cCode.function(code = """ - |bool __FUNCTION__(FILE* this, int32_t x) { + |static bool __FUNCTION__(FILE* this, int32_t x) { | return fprintf(this, "%"PRIi32, x) >= 0; |} """, @@ -150,7 +150,7 @@ case class FileOutputStream private (var filename: Option[String]) { @extern @cCode.function(code = """ - |bool __FUNCTION__(FILE* this, char c) { + |static bool __FUNCTION__(FILE* this, char c) { | return fprintf(this, "%c", c) >= 0; |} """ @@ -175,7 +175,7 @@ case class FileOutputStream private (var filename: Option[String]) { @extern @cCode.function(code = """ - |bool __FUNCTION__(FILE* this, char* s) { + |static bool __FUNCTION__(FILE* this, char* s) { | return fprintf(this, "%s", s) >= 0; |} """ diff --git a/library/leon/io/StdIn.scala b/library/leon/io/StdIn.scala index 5f83ad8fd..39356b0ba 100644 --- a/library/leon/io/StdIn.scala +++ b/library/leon/io/StdIn.scala @@ -52,7 +52,7 @@ object StdIn { // (which assumes CHAR_BITS == 8) because SCNi8 will read char '0' to '9' // and not the "raw" data. @cCode.function(code = """ - |int8_t __FUNCTION__(void** unused, bool* valid) { + |static int8_t __FUNCTION__(void** unused, bool* valid) { | int8_t x; | *valid = scanf("%c", &x) == 1; | return x; @@ -99,7 +99,7 @@ object StdIn { // Because this is a nested function, contexts variables are passes by reference. @cCode.function(code = """ - |int32_t __FUNCTION__(void** unused, bool* valid) { + |static int32_t __FUNCTION__(void** unused, bool* valid) { | int32_t x; | *valid = scanf("%"SCNd32, &x) == 1; | return x; diff --git a/library/leon/io/StdOut.scala b/library/leon/io/StdOut.scala index a11cafd13..2f7d91a47 100644 --- a/library/leon/io/StdOut.scala +++ b/library/leon/io/StdOut.scala @@ -10,7 +10,7 @@ object StdOut { @library @cCode.function( code = """ - |void __FUNCTION__(char* s) { + |static void __FUNCTION__(char* s) { | printf("%s", s); |} """, @@ -34,7 +34,7 @@ object StdOut { @extern @cCode.function( code = """ - |void __FUNCTION__(int8_t x) { + |static void __FUNCTION__(int8_t x) { | printf("%c", x); |} """, @@ -55,7 +55,7 @@ object StdOut { @extern @cCode.function( code = """ - |void __FUNCTION__(int32_t x) { + |static void __FUNCTION__(int32_t x) { | printf("%"PRIi32, x); |} """, @@ -75,7 +75,7 @@ object StdOut { @extern @cCode.function( code = """ - |void __FUNCTION__(char c) { + |static void __FUNCTION__(char c) { | printf("%c", c); |} """, diff --git a/library/leon/io/package.scala b/library/leon/io/package.scala index f3cb30e54..d4dc251f9 100644 --- a/library/leon/io/package.scala +++ b/library/leon/io/package.scala @@ -11,7 +11,7 @@ package object io { case class State(var seed: BigInt) @library - @cCode.function(code = "void* __FUNCTION__(void) { return NULL; }") + @cCode.function(code = "static void* __FUNCTION__(void) { return NULL; }") def newState: State = State(0) } From 76b546a8739293685acc1ade727aec82e34e70f8 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 17 Oct 2016 14:34:26 +0200 Subject: [PATCH 54/77] Add a first implementation of LZW for GenC --- .../regression/genc/unverified/LZW.scala | 461 ++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 src/test/resources/regression/genc/unverified/LZW.scala diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala new file mode 100644 index 000000000..4aef84ffa --- /dev/null +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -0,0 +1,461 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.lang._ +import leon.proof._ +import leon.annotation.{ extern, inline, cCode } + +import leon.io.{ + FileInputStream => FIS, + FileOutputStream => FOS, + StdOut +} + +object LZW { + + // GENERAL NOTES + // ============= + // + // Encoding using fixed size of word; + // Input alphabet is the ASCII range (0-255); // 1-26 + // A word is made of 16 bits (instead of the classic 12-bit scenario, for simplicity); + // The dictionary is an array of 32-bit integers where the index is the key; + // TODO s/index/codeword + // TODO 16-bit integers would be useful here!; + + // We limit the size of the dictionary to 2^10 + @inline + val DICTIONARY_SIZE = 1024 + + // We use fix-sized buffers + @inline + val BUFFER_SIZE = 10 // characters + + @inline + val MAX_ARRAY_SIZE = 2048 // Not sure if relevent + + @inline + val MAX_INT_VALUE = 26 + + @inline + val MIN_INT_VALUE = 1 // 0 is for EOF + + @inline + val ALPHABET_SIZE = MAX_INT_VALUE - MIN_INT_VALUE + + // TODO reading cannot fail ATM. We assume reading 0 means EOF + @inline + val EOF = 0 + + private def leammaArraySizes: Boolean = { + DICTIONARY_SIZE <= MAX_ARRAY_SIZE && DICTIONARY_SIZE > 0 && + BUFFER_SIZE <= MAX_ARRAY_SIZE && BUFFER_SIZE > 0 && + DICTIONARY_SIZE > ALPHABET_SIZE // min number of elements + }.holds + + // A buffer representation using a fix-sized array for memory. + // + // NOTE Use `createBuffer()` to get a new buffer; don't attempt to create one yourself. + // + // NOTE please don't edit length from outside Buffer! + case class Buffer(val array: Array[Int], var length: Int) { + require(array.length > 0 && array.length <= MAX_ARRAY_SIZE && length >= 0 && length <= array.length) + + def isEqual(b: Buffer): Boolean = { + if (b.length != length) false + else { + var i = 0 + var matching = true + + while (matching && i < length) { + if (array(i) != b.array(i)) + matching = false + + i = i + 1 + } + + matching + } + } + + def apply(index: Int): Int = { + require(index >= 0 && index < length) + array(index) + } + + def append(x: Int): Unit = { + require(notFull) + + array(length) = x + + length = length + 1 + } ensuring { u => + length <= maxLength + } + + def dropLast(): Unit = { + require(length > 0) + + length = length - 1 + } ensuring { u => + length >= 0 + } + + def clear(): Unit = { + length = 0 + } ensuring { u => + isEmpty + } + + def set(b: Buffer) { + require(array.length >= b.length) + length = b.length + + var i = 0 + while (i < length) { + array(i) = b.array(i) + i = i + 1 + } + } + + def maxLength: Int = array.length + + def isFull: Boolean = length == maxLength + + def notFull: Boolean = length >= 0 && length < maxLength + + def isEmpty: Boolean = length == 0 + + def nonEmpty: Boolean = length >= 0 + + } + + @inline // very important because we cannot return arrays + def createBuffer(): Buffer = { + Buffer(Array.fill(BUFFER_SIZE)(0), 0) + } ensuring { b => b.isEmpty && b.notFull } + + // Read the given input file `fis`, encode its content, save the encoded + // version into `fos`, and return true on success. + // + // The algorithm is: + // 0a. init the dictionary to support the full input alphabet + // 0b. set P to empty (P below is our `buffer`) + // 1. read the next character C (a.k.a. `current`) + // 2. append C to P (i.e. "P = P :: C") + // 3. check if the dictionary has an entry for the current buffer P + // 4. if yes, keep the buffer P as is (i.e. with C at the end) + // 5a. if not, remove C from the buffer P, + // 5b. output the codeword associated with P + // 5c. insert "P :: C" into the dictionary to create a new codeword + // 5d. set P to the monocharacter string C + // 6. goto 1 if more characters available + // 7. P is non empty! So find the corresponding codeword and output it + def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) + + // 0a. Init dictionary with all basic range + val (dict, idx) = initDictionary() + var nextIndex = idx + + // 0b. Create an empty buffer + val buffer = createBuffer() + + // 1. Read the next input + // TODO we need to be able to read Byte! + var current = readByteAsInt(fis) + + // We cannot use `return` to shortcircuit the function and do an early exit, + // so we keep an abort boolean and use if-statements to wrap actions. + var abort = false + + ( + while (!abort && current != EOF) { + // 2. Append input to the buffer + buffer.append(current) + + // 3. Check if the entry is known + val (found, index) = findIndex(dict, nextIndex, buffer) + + if (!found) { + // 5a. Restore previous buffer + buffer.dropLast() + + // 5b. Output the current codeword + val (found2, index2) = findIndex(dict, nextIndex, buffer) + assert(found2) + if (!outputCodeword(index2, fos)) + abort = true + + // 5c. Insert the extended codeword in the dictionary + buffer.append(current) + nextIndex = insertWord(nextIndex, buffer, dict) + + // 5d. Prepare buffer for next round + buffer.clear() + buffer.append(current) + } + // else: 4. Noop + + // Gracefully exit if we cannot encode the next byte + if (buffer.isFull || nextIndex == dict.length) + abort = true + + // 6. Read next char for next while loop iteration + current = readByteAsInt(fis) + } + ) + + assert(buffer.nonEmpty) + assert((!abort) ==> (current == EOF)) + + // 7. Process the remaining buffer + if (!abort) { + val (found, index) = findIndex(dict, nextIndex, buffer) + // Either at step 3 the buffer was: + // - found in the dictionary and the buffer was not altered (we just read EOF), or + // - not found in the dictionary and therefore the buffer now contains only one character. + assert(found) + + if (!outputCodeword(index, fos)) + abort = true + } + + // Add EOF marker + abort = abort || !fos.write(EOF) + + !abort + } + + // Read the given input file `fis`, decode its content, save the decoded + // version into `fos`, and return true on success. + // + // The algorithm is: + // 0a. init the dictionary to support the full input alphabet + // 0b. create a buffer, w + // 1. read the next codeword c + // 2a. if not EOF: + // 2b. append c to w + // 2c. output c + // 2d. read the next codeword c + // 3. if EOF stop + // 4a. if c is in dictionary (i.e. c < |dict|), set entry to the corresponding value + // 4b. else if c == |dict|, set entry to w, then append the first character of w to entry + // 4c. else abort (decoding error) + // 5. output entry + // 6a. set tmp to w and then append the first character of entry to tmp + // 6b. add tmp at the end of the dictionary + // 7. set w to entry + // 8. goto 4 if more codewords available + def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) + + // 0a. Init dictionary with all basic range + val (dict, idx) = initDictionary() + var nextIndex = idx + + // 0b. Create an empty buffer + val w = createBuffer() + + // 1. Read the next input + // TODO we need to be able to read Byte! + var c = readCodeword(fis) + + // We cannot use `return` to shortcircuit the function and do an early exit, + // so we keep an abort boolean and use if-statements to wrap actions. + var abort = false + + // 2a-2d: process the first codeword + if (c != EOF) { + w.append(c) + abort = !outputBuffer(w, fos) + c = readCodeword(fis) + } + + // Process the remaining codewords + while (!abort && c != EOF) { + // 4a-4c: process current codeword + val entry = createBuffer() + if (c < nextIndex) { + entry.set(dict(c)) + } else if (c == nextIndex) { + entry.set(w) + entry.append(w(0)) + } else { + abort = true + } + + // Make sure we haven't used the full buffer w or we won't be able to append something; + // Gracefully exit if we cannot decode the next codeword; + // 5. output the current entry + abort = abort || w.isFull || nextIndex == dict.length || !outputBuffer(entry, fos) + + if (!abort) { + // 6a-6b. augment the dictionary + val tmp = createBuffer() + tmp.set(w) + tmp.append(entry(0)) + nextIndex = insertWord(nextIndex, tmp, dict) + + // 7. prepare for next codeword + w.set(entry) + + // 8. if more codewords available, process them + c = readCodeword(fis) + } + } + + // Add EOF marker + abort = abort || !fos.write(EOF) + + !abort + } + + def outputCodeword(index: Int, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fos.isOpen) + fos.write(index) && fos.write(" ") + } + + def outputBuffer(b: Buffer, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fos.isOpen) + + var i = 0 + var success = true + + while (success && i < b.length) { + success = fos.write(b(i)) && fos.write(" ") + i = i + 1 + } + + success + } + + // TODO wrap dictionary into a class? + + // Init the dictionary with the ASCII range encoding and return the next free index in the dictionary + @inline + def initDictionary(): (Array[Buffer], Int) = { + val dict = Array.fill(DICTIONARY_SIZE) { createBuffer() } + + var i = 0 + + while (i <= MAX_INT_VALUE) { + dict(i).append(i) + + i = i + 1 + } + + (dict, i) + } + + // Attempt to find `buffer` in the given `dict`. + // Return (true, idx) if found, otherwise (false, _). + def findIndex(dict: Array[Buffer], dictSize: Int, buffer: Buffer): (Boolean, Int) = { + var idx = 0 + var found = false + + while (!found && idx < dictSize) { + + if (dict(idx).isEqual(buffer)) + found = true + else + idx = idx + 1 + } + + (found, idx) + } + + // Insert a given word (`buffer`) into `dict` at the given `index` and return the next index + def insertWord(index: Int, buffer: Buffer, dict: Array[Buffer]): Int = { + require(0 <= index && index < dict.length) + dict(index).set(buffer) + index + 1 + } + + def isValidInput(x: Int) = x >= MIN_INT_VALUE && x <= MAX_INT_VALUE + + def readByteAsInt(fis: FIS)(implicit state: leon.io.State): Int = { + require(fis.isOpen) + val x = fis.readInt + + // huhu, I'm clearly cheating here!!! + if (x < 0) 0 // 0 and not MAX_INT_VALUE as we need to handle EOF + else if (x > MAX_INT_VALUE) MAX_INT_VALUE + else x + } ensuring { res => res == EOF || isValidInput(res) } + + def readCodeword(fis: FIS)(implicit state: leon.io.State): Int = { + require(fis.isOpen) + fis.readInt + } + + + @inline + val SUCCESS = 0 + + @inline + val OPEN_ERROR = -1 + + @inline + val ENCODE_ERROR = 1 + + @inline + val DECODE_ERROR = 2 + + def _main() = { + implicit val state = leon.io.newState + + + def status(x: Int): Int = { + x match { + case x if x == SUCCESS => StdOut.println("success") + case x if x == OPEN_ERROR => StdOut.println("couldn't open file") + case x if x == ENCODE_ERROR => StdOut.println("encoding failed") + case x if x == DECODE_ERROR => StdOut.println("decoding failed") + case _ => StdOut.print("unknown error "); StdOut.println(x) + } + + x + } + + def encodeFile(): Int = { + val input = FIS.open("input.txt") + val encoded = FOS.open("encoded.txt") + + val res = + if (input.isOpen && encoded.isOpen) { + if (encode(input, encoded)) status(SUCCESS) + else status(ENCODE_ERROR) + } else status(OPEN_ERROR) + + encoded.close + input.close + + res + } + + def decodeFile(): Int = { + val encoded = FIS.open("encoded.txt") + val decoded = FOS.open("decoded.txt") + + val res = + if (encoded.isOpen && decoded.isOpen) { + if (decode(encoded, decoded)) status(SUCCESS) + else status(DECODE_ERROR) + } else status(OPEN_ERROR) + + decoded.close + encoded.close + + res + } + + val r1 = encodeFile() + if (r1 == SUCCESS) decodeFile() + else r1 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + From 2855b6bc40d4366c196e58609ad991b2f1c19853 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 9 Dec 2016 18:14:38 +0100 Subject: [PATCH 55/77] LZW v2.dirty --- .../regression/genc/unverified/LZW.scala | 1156 +++++++++++++---- 1 file changed, 885 insertions(+), 271 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index 4aef84ffa..8500e917e 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -2,7 +2,7 @@ import leon.lang._ import leon.proof._ -import leon.annotation.{ extern, inline, cCode } +import leon.annotation._ import leon.io.{ FileInputStream => FIS, @@ -16,123 +16,318 @@ object LZW { // ============= // // Encoding using fixed size of word; - // Input alphabet is the ASCII range (0-255); // 1-26 + // Input alphabet is the ASCII range (0-255); // A word is made of 16 bits (instead of the classic 12-bit scenario, for simplicity); - // The dictionary is an array of 32-bit integers where the index is the key; - // TODO s/index/codeword - // TODO 16-bit integers would be useful here!; + // The dictionary is an array of ??? where the index is the key; + // TODO s/index/codeword -> better yet: define index2codeword conversion (Int -> (Byte, Byte)) + // TODO 16-bit integers would be useful here! // We limit the size of the dictionary to 2^10 @inline - val DICTIONARY_SIZE = 1024 + val DictionarySize = 1024 // We use fix-sized buffers @inline - val BUFFER_SIZE = 10 // characters + val BufferSize = 10 // characters - @inline - val MAX_ARRAY_SIZE = 2048 // Not sure if relevent + val AlphabetSize = Byte.MaxValue + -Byte.MinValue - @inline - val MAX_INT_VALUE = 26 + private def lemmaSize: Boolean = { + DictionarySize >= AlphabetSize && + BufferSize > 0 && + AlphabetSize > 0 && + DictionarySize <= 1000000 + }.holds - @inline - val MIN_INT_VALUE = 1 // 0 is for EOF + private def lemmaBufferFull(b: Buffer): Boolean = { + b.isFull == !b.nonFull + }.holds - @inline - val ALPHABET_SIZE = MAX_INT_VALUE - MIN_INT_VALUE + private def lemmaBufferEmpty(b: Buffer): Boolean = { + b.isEmpty == !b.nonEmpty + }.holds - // TODO reading cannot fail ATM. We assume reading 0 means EOF - @inline - val EOF = 0 + private def lemmaBufferFullEmpty1(b: Buffer): Boolean = { + b.isFull ==> b.nonEmpty + }.holds - private def leammaArraySizes: Boolean = { - DICTIONARY_SIZE <= MAX_ARRAY_SIZE && DICTIONARY_SIZE > 0 && - BUFFER_SIZE <= MAX_ARRAY_SIZE && BUFFER_SIZE > 0 && - DICTIONARY_SIZE > ALPHABET_SIZE // min number of elements + private def lemmaBufferFullEmpty2(b: Buffer): Boolean = { + (BufferSize > 0 && b.isEmpty) ==> b.nonFull }.holds - // A buffer representation using a fix-sized array for memory. - // - // NOTE Use `createBuffer()` to get a new buffer; don't attempt to create one yourself. - // - // NOTE please don't edit length from outside Buffer! - case class Buffer(val array: Array[Int], var length: Int) { - require(array.length > 0 && array.length <= MAX_ARRAY_SIZE && length >= 0 && length <= array.length) + private def lemmaBufferSelfEqual(b: Buffer): Boolean = { + b.isEqual(b) + }.holds - def isEqual(b: Buffer): Boolean = { - if (b.length != length) false + private def lemmaBufferEqual(a: Buffer, b: Buffer): Boolean = { + b.isEqual(a) ==> a.isEqual(b) + }.holds + + private def lemmaBufferEqualImmutable(a: Buffer, b: Buffer): Unit = { + a.isEqual(b) + } ensuring { _ => old(a).isEqual(a) && old(b).isEqual(b) } + + private def lemmaDictionaryFull(d: Dictionary): Boolean = { + d.isFull == !d.nonFull + }.holds + + private def lemmaDictionaryFullEmpty(d: Dictionary): Boolean = { + d.isEmpty ==> d.nonFull + }.holds + + private def lemmaSelfRangeEqual(a: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length) + isRangeEqual(a, a, from, to) because { + if (from == to) check { lemmaUnitRangeEqual(a, a, from) } else { - var i = 0 - var matching = true + check { a(from) == a(from) } && + check { a(to) == a(to) } && + check { lemmaSelfRangeEqual(a, from, to - 1) } && + check { lemmaSelfRangeEqual(a, from + 1, to) } + } + } + }.holds + + private def lemmaRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) - while (matching && i < length) { - if (array(i) != b.array(i)) - matching = false + ( isRangeEqual(a, b, from, to) ==> isRangeEqual(b, a, from, to) ) // FIXME timeout + }.holds + + private def lemmaSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + check { a(from) == b(from) } && + check { (from + 1 <= to) ==> isRangeEqual(a, b, from + 1, to) } + ) + }.holds - i = i + 1 + private def lemmaSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + check { (a(to) == b(to)) because lemmaRangeEndEqual(a, b, from, to) } && + check { (from <= to - 1) ==> isRangeEqual(a, b, from, to - 1) } // FIXME timeout + ) + }.holds // FIXME TIMEOUT + + private def lemmaMiniSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + isRangeEqual(a, b, from, from) because a(from) == b(from) + ) + }.holds + + private def lemmaMiniSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + isRangeEqual(a, b, to, to) because { + if (from == to) check { a(to) == b(to) } + else { + check { from < to } && + check { lemmaMiniSubRangeEqual2(a, b, from + 1, to) } && + check { lemmaMiniSubRangeEqual2(a, b, from, to - 1) } } + } + ) + }.holds - matching + private def lemmaUnitRangeEqual(a: Array[Byte], b: Array[Byte], pos: Int): Boolean = { + require(0 <= pos && pos < a.length && pos < b.length) + isRangeEqual(a, b, pos, pos) ==> (a(pos) == b(pos)) + }.holds + + private def lemmaRangeEndEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length && isRangeEqual(a, b, from, to)) + ( a(to) == b(to) ) because { + if (from == to) trivial + else { + check { from < to } && + check { lemmaRangeEndEqual(a, b, from + 1, to) } } } + }.holds + + + private def lemmaCodeWordIndexEquality(index: Int): Boolean = { + require(0 <= index && index < 65536) + index == codeWord2Index(index2CodeWord(index)) + }.holds + + + private def lemmaAllValidBuffers(buffers: Array[Buffer]): Boolean = { + allValidBuffers(buffers) + }.holds // FIXME TIMEOUT + + + + private def testIsRangeEqual(): Boolean = { + val a = Array[Byte](1, 0, 0, 0, 0, 0, 0, 0, 0, 0) + val b = Array[Byte](-128, 0, 0, 0, 0, 0, 0, 0, 0, 0) + !isRangeEqual(a, b, 0, 0) + }.holds + + + private def testBufferIsEqual(): Boolean = { + val a = createBuffer() + val b = createBuffer() + + a.append(1) + b.append(-128) + + assert(a.size == b.size) + assert(a.nonEmpty) + assert(b.nonEmpty) + assert(a.isEmpty == b.isEmpty) + + !a.isEqual(b) + }.holds + + // Helper for range equality checking + private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + a(from) == b(from) && { + if (from == to) true + else isRangeEqual(a, b, from + 1, to) + } + } + - def apply(index: Int): Int = { + private def allValidBuffers(buffers: Array[Buffer]): Boolean = { + def rec(from: Int): Boolean = { + require(0 <= from && from <= buffers.length) + if (from < buffers.length) buffers(from).isValid && rec(from + 1) + else true + } + + rec(0) + } + + + // A buffer representation using a fix-sized array for memory. + // + // NOTE Use `createBuffer()` to get a new buffer; don't attempt to create one yourself. + case class Buffer(private val array: Array[Byte], private var length: Int) { + val capacity = array.length + require(isValid) + + def isValid: Boolean = length >= 0 && length <= capacity && capacity == BufferSize + + def isFull: Boolean = length == capacity + + def nonFull: Boolean = length < capacity + + def isEmpty: Boolean = length == 0 + + def nonEmpty: Boolean = length > 0 + + def isEqual(b: Buffer): Boolean = { + if (b.length != length) false + else { isEmpty || isRangeEqual(array, b.array, 0, length - 1) } + } //ensuring { _ => this.isEqual(old(this)) && b.isEqual(old(b)) } // this VC does infinite recursion + + def size = length + + def apply(index: Int): Byte = { require(index >= 0 && index < length) array(index) } - def append(x: Int): Unit = { - require(notFull) + def append(x: Byte): Unit = { + require(nonFull) array(length) = x - length = length + 1 - } ensuring { u => - length <= maxLength - } + length += 1 + } ensuring { _ => isValid } def dropLast(): Unit = { - require(length > 0) + require(nonEmpty) - length = length - 1 - } ensuring { u => - length >= 0 - } + length -= 1 + } ensuring { _ => isValid } //&& old(this).length == length + 1 } def clear(): Unit = { length = 0 - } ensuring { u => - isEmpty - } + } ensuring { _ => isEmpty && isValid } - def set(b: Buffer) { - require(array.length >= b.length) - length = b.length + def set(b: Buffer): Unit = { + if (b.isEmpty) clear + else setImpl(b) + } ensuring { _ => b.isValid && isValid && isEqual(b) /* && b.isEqual(old(b)) */ } - var i = 0 - while (i < length) { - array(i) = b.array(i) - i = i + 1 - } - } + private def setImpl(b: Buffer): Unit = { + require(b.nonEmpty) - def maxLength: Int = array.length + length = b.length - def isFull: Boolean = length == maxLength + assert(b.isValid) + assert(isValid) + assert(nonEmpty) + assert(length == b.length) - def notFull: Boolean = length >= 0 && length < maxLength + var i = 0 + (while (i < length) { + assert(isValid) + assert(nonEmpty) + assert(length == b.length) + assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) - def isEmpty: Boolean = length == 0 + array(i) = b.array(i) + i += 1 + + assert(isValid) + assert(nonEmpty) + assert(length == b.length) + assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) + }) invariant { // FIXME TIMEOUT + 0 <= i && i <= length && + // lengthCheckpoint == b.length && lengthCheckpoint == length && // no mutation of the length + isValid && nonEmpty && + length == b.length && + (i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) // avoid OutOfBoundAccess + } - def nonEmpty: Boolean = length >= 0 + assert(b.isValid) + assert(isValid) + assert(nonEmpty) + assert(isRangeEqual(array, b.array, 0, length - 1)) + assert(length == b.length) + assert(isEqual(b)) + } ensuring { _ => b.isValid && isValid && nonEmpty && isEqual(b) /* && b.isEqual(old(b)) */ } + +/* + * def set(b: Buffer): Unit = { + * require(array.length == b.length) // all buffers have the same capacity + * + * var i = 0 + * (while (i < b.length) { + * array(i) = b.array(i) + * i = i + 1 + * }) invariant { + * i >= 0 && i <= b.length && + * array.length >= b.length && + * isRangeEqual(array, b.array, 0, i) + * } + * + * length = b.length + * } ensuring { _ => this.isEqual(b) } + */ } + /* + * private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + * require(0 <= from && from <= to && to < a.length && a.length == b.length) + * if (from == to) true else { + * // a(from) == b(from) && (from <= to ==> isRangeEqual(a, b, from + 1, to)) + * } + * } + */ + @inline // very important because we cannot return arrays def createBuffer(): Buffer = { - Buffer(Array.fill(BUFFER_SIZE)(0), 0) - } ensuring { b => b.isEmpty && b.notFull } + Buffer(Array.fill(BufferSize)(0), 0) + } ensuring { b => b.isEmpty && b.nonFull && b.isValid } // Read the given input file `fis`, encode its content, save the encoded // version into `fos`, and return true on success. @@ -150,81 +345,92 @@ object LZW { // 5d. set P to the monocharacter string C // 6. goto 1 if more characters available // 7. P is non empty! So find the corresponding codeword and output it - def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { - require(fis.isOpen && fos.isOpen) - - // 0a. Init dictionary with all basic range - val (dict, idx) = initDictionary() - var nextIndex = idx - - // 0b. Create an empty buffer - val buffer = createBuffer() - - // 1. Read the next input - // TODO we need to be able to read Byte! - var current = readByteAsInt(fis) - - // We cannot use `return` to shortcircuit the function and do an early exit, - // so we keep an abort boolean and use if-statements to wrap actions. - var abort = false - - ( - while (!abort && current != EOF) { - // 2. Append input to the buffer - buffer.append(current) - - // 3. Check if the entry is known - val (found, index) = findIndex(dict, nextIndex, buffer) - - if (!found) { - // 5a. Restore previous buffer - buffer.dropLast() - - // 5b. Output the current codeword - val (found2, index2) = findIndex(dict, nextIndex, buffer) - assert(found2) - if (!outputCodeword(index2, fos)) - abort = true - - // 5c. Insert the extended codeword in the dictionary - buffer.append(current) - nextIndex = insertWord(nextIndex, buffer, dict) - - // 5d. Prepare buffer for next round - buffer.clear() - buffer.append(current) - } - // else: 4. Noop - - // Gracefully exit if we cannot encode the next byte - if (buffer.isFull || nextIndex == dict.length) - abort = true - - // 6. Read next char for next while loop iteration - current = readByteAsInt(fis) - } - ) - - assert(buffer.nonEmpty) - assert((!abort) ==> (current == EOF)) - - // 7. Process the remaining buffer - if (!abort) { - val (found, index) = findIndex(dict, nextIndex, buffer) - // Either at step 3 the buffer was: - // - found in the dictionary and the buffer was not altered (we just read EOF), or - // - not found in the dictionary and therefore the buffer now contains only one character. - assert(found) - - if (!outputCodeword(index, fos)) - abort = true - } - - // Add EOF marker - abort = abort || !fos.write(EOF) - - !abort - } +/* + * def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + * require(fis.isOpen && fos.isOpen) + * + * // 0a. Init dictionary with all basic range + * val (dict, idx) = initDictionary() + * + * encodeImpl(fis, fos, dict, idx) + * } + */ + +/* + * def encodeImpl(fis: FIS, fos: FOS, dict: Array[Buffer], idx: Int)(implicit state: leon.io.State): Boolean = { + * require(fis.isOpen && fos.isOpen && dict.length == DICTIONARY_SIZE && 0 <= idx && idx <= dict.length && allValidBuffer(dict)) + * + * var nextIndex = idx + * + * // 0b. Create an empty buffer + * val buffer = createBuffer() + * + * // 1. Read the next input + * var current = readByte(fis) + * + * // We cannot use `return` to shortcircuit the function and do an early exit, + * // so we keep an abort boolean and use if-statements to wrap actions. + * var abort = false + * + * ( + * while (!abort && current.isDefined) { + * // 2. Append input to the buffer + * buffer.append(current.get) + * + * // 3. Check if the entry is known + * assert(dict.length == DICTIONARY_SIZE) + * val index = findIndex(dict, nextIndex, buffer) + * + * if (index.isEmpty) { + * // 5a. Restore previous buffer + * buffer.dropLast() + * + * // 5b. Output the current codeword + * val index2 = findIndex(dict, nextIndex, buffer) + * // By construction, the index will be found but this fact is not proven so we have to check for it + * if (index2.isEmpty || !outputCodeword(index2.get, fos)) + * abort = true + * + * // 5c. Insert the extended codeword in the dictionary + * buffer.append(current.get) + * nextIndex = insertWord(nextIndex, buffer, dict) + * + * // 5d. Prepare buffer for next round + * buffer.clear() + * buffer.append(current.get) + * } + * // else: 4. Noop + * + * // Gracefully exit if we cannot encode the next byte + * if (buffer.isFull || nextIndex == dict.length) + * abort = true + * + * // 6. Read next char for next while loop iteration + * current = readByte(fis) + * } + * ) invariant { + * (!abort ==> (buffer.notFull && nextIndex < dict.length)) && + * dict.length == DICTIONARY_SIZE && + * 0 <= nextIndex && nextIndex <= dict.length && + * allValidBuffer(dict) + * } + * + * // 7. Process the remaining buffer, if any + * if (!abort && buffer.nonEmpty) { + * assert(dict.length == DICTIONARY_SIZE) + * val index = findIndex(dict, nextIndex, buffer) + * // Either at step 3 the buffer was: + * // - found in the dictionary and the buffer was not altered (we just read EOF), or + * // - not found in the dictionary and therefore the buffer now contains only one character. + * assert(index.isDefined) + * + * if (!outputCodeword(index.get, fos)) + * abort = true + * } + * + * !abort + * } + */ // Read the given input file `fis`, decode its content, save the decoded // version into `fos`, and return true on success. @@ -246,186 +452,595 @@ object LZW { // 6b. add tmp at the end of the dictionary // 7. set w to entry // 8. goto 4 if more codewords available - def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { - require(fis.isOpen && fos.isOpen) +/* + * def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + * require(fis.isOpen && fos.isOpen) + * + * // 0a. Init dictionary with all basic range + * val (dict, idx) = initDictionary() + * var nextIndex = idx + * + * // 0b. Create an empty buffer + * val w = createBuffer() + * + * // 1. Read the next input + * // TODO we need to be able to read Byte! + * var c = readCodeword(fis) + * + * // We cannot use `return` to shortcircuit the function and do an early exit, + * // so we keep an abort boolean and use if-statements to wrap actions. + * var abort = false + * + * // 2a-2d: process the first codeword + * if (c != EOF) { + * w.append(c) + * abort = !outputBuffer(w, fos) + * c = readCodeword(fis) + * } + * + * // Process the remaining codewords + * while (!abort && c != EOF) { + * // 4a-4c: process current codeword + * val entry = createBuffer() + * if (c < nextIndex) { + * entry.set(dict(c)) + * } else if (c == nextIndex) { + * entry.set(w) + * entry.append(w(0)) + * } else { + * abort = true + * } + * + * // Make sure we haven't used the full buffer w or we won't be able to append something; + * // Gracefully exit if we cannot decode the next codeword; + * // 5. output the current entry + * abort = abort || w.isFull || nextIndex == dict.length || !outputBuffer(entry, fos) + * + * if (!abort) { + * // 6a-6b. augment the dictionary + * val tmp = createBuffer() + * tmp.set(w) + * tmp.append(entry(0)) + * nextIndex = insertWord(nextIndex, tmp, dict) + * + * // 7. prepare for next codeword + * w.set(entry) + * + * // 8. if more codewords available, process them + * c = readCodeword(fis) + * } + * } + * + * // Add EOF marker + * abort = abort || !fos.write(EOF) + * + * !abort + * } + */ + + /* + * def outputCodeword(index: Int, fos: FOS)(implicit state: leon.io.State): Boolean = { + * require(fos.isOpen) + * val b2 = index.toByte + * val b1 = (index >>> 8).toByte + * fos.write(b1) && fos.write(b2) + * } + */ + +/* + * def outputBuffer(b: Buffer, fos: FOS)(implicit state: leon.io.State): Boolean = { + * require(fos.isOpen) + * + * var i = 0 + * var success = true + * + * while (success && i < b.length) { + * success = fos.write(b(i)) && fos.write(" ") + * i = i + 1 + * } + * + * success + * } + */ + + // TODO wrap dictionary into a class? - // 0a. Init dictionary with all basic range - val (dict, idx) = initDictionary() - var nextIndex = idx + // Init the dictionary with the range of Byte and return the next free index in the dictionary +/* + * @inline + * def initDictionary(): (Array[Buffer], Int) = { + * val dict = Array.fill(DICTIONARY_SIZE) { createBuffer() } + * assert(dict.length == DICTIONARY_SIZE) + * assert(allValidBuffer(dict)) + * // assert(arrayForall(dict, { buffer: Buffer => buffer.isEmpty && buffer.notFull })) // not supported??? + * + * var index = 0 + * var value: Int = Byte.MinValue // Use an Int to avoid overflow issues + * + * (while (value <= Byte.MaxValue) { + * assert(Byte.MinValue <= value && value <= Byte.MaxValue) + * assert(dict(index).notFull) // this fails for some reason + * dict(index).append(value.toByte) // safe conversion, no loss of information + * + * index += 1 + * value += 1 // last iteration would overflow on Byte but not on Int + * }) invariant { + * value >= Byte.MinValue && value <= Byte.MaxValue + 1 && + * index >= 0 && index <= DICTIONARY_SIZE && + * index == value + -Byte.MinValue && // they increment at the same speed + * dict.length == DICTIONARY_SIZE && + * allValidBuffer(dict) + * } + * + * (dict, index) + * } ensuring { res => allValidBuffer(res._1) && res._2 == ALPHABET_SIZE } + */ - // 0b. Create an empty buffer - val w = createBuffer() + // Attempt to find `buffer` in the given `dict`. +/* + * def findIndex(dict: Array[Buffer], dictSize: Int, buffer: Buffer): Option[Int] = { + * require(dict.length == DICTIONARY_SIZE && 0 <= dictSize && dictSize <= dict.length && allValidBuffer(dict)) + * + * var idx = 0 + * var found = false + * + * (while (!found && idx < dictSize) { + * found = dict(idx).isEqual(buffer) + * // idx = idx + 1 // buggy!!! increement only when not found!, counter example was found!!! + * if (!found) + * idx += 1 + * }) ensuring { + * idx >= 0 && idx <= dictSize + * } + * + * if (found) Some[Int](idx) else None[Int]() + * } ensuring { res => + * // (res.isDefined == arrayExists(dict, 0, dictSize - 1, { elem: Buffer => elem.isEqual(buffer) })) && // Not supported??? + * (res.isDefined ==> dict(res.get).isEqual(buffer)) && + * dict.length == DICTIONARY_SIZE + * } + * + */ + // Insert a given word (`buffer`) into `dict` at the given `index` and return the next index +/* + * def insertWord(index: Int, buffer: Buffer, dict: Array[Buffer]): Int = { + * require(0 <= index && index < dict.length && dict.length == DICTIONARY_SIZE && allValidBuffer(dict)) + * + * dict(index).set(buffer) + * index + 1 + * } ensuring { res => + * 0 < res && res <= dict.length && + * dict.length == DICTIONARY_SIZE + * } + */ + + def tryReadNext(fis: FIS)(implicit state: leon.io.State): Option[Byte] = { + require(fis.isOpen) + val b = fis.tryReadByte() + StdOut.print("Reading next byte: ") + if (b.isDefined) { + val byte = b.get + if (0x20 <= byte && byte <= 0x7e) { + StdOut.print(byte) + } else { + StdOut.print("[") + StdOut.print(byte.toInt) + StdOut.print("]") + } + } else StdOut.print("EOF") + StdOut.println() + b + } - // 1. Read the next input - // TODO we need to be able to read Byte! - var c = readCodeword(fis) + def writeCodeWord(fos: FOS, cw: CodeWord): Boolean = { + require(fos.isOpen) + StdOut.print("Writing codeword for index "); val index = codeWord2Index(cw); StdOut.println(index) + fos.write(cw.b1) && fos.write(cw.b2) + } - // We cannot use `return` to shortcircuit the function and do an early exit, - // so we keep an abort boolean and use if-statements to wrap actions. - var abort = false + def tryReadCodeWord(fis: FIS)(implicit state: leon.io.State): Option[CodeWord] = { + require(fis.isOpen) + val b1Opt = fis.tryReadByte() + val b2Opt = fis.tryReadByte() - // 2a-2d: process the first codeword - if (c != EOF) { - w.append(c) - abort = !outputBuffer(w, fos) - c = readCodeword(fis) + (b1Opt, b2Opt) match { + case (Some(b1), Some(b2)) => Some(CodeWord(b1, b2)) + case _ => None() } + } - // Process the remaining codewords - while (!abort && c != EOF) { - // 4a-4c: process current codeword - val entry = createBuffer() - if (c < nextIndex) { - entry.set(dict(c)) - } else if (c == nextIndex) { - entry.set(w) - entry.append(w(0)) - } else { - abort = true - } + def writeBytes(fos: FOS, buffer: Buffer): Boolean = { + require(fos.isOpen && buffer.nonEmpty) + var success = true + var i = 0 - // Make sure we haven't used the full buffer w or we won't be able to append something; - // Gracefully exit if we cannot decode the next codeword; - // 5. output the current entry - abort = abort || w.isFull || nextIndex == dict.length || !outputBuffer(entry, fos) + val size = buffer.size - if (!abort) { - // 6a-6b. augment the dictionary - val tmp = createBuffer() - tmp.set(w) - tmp.append(entry(0)) - nextIndex = insertWord(nextIndex, tmp, dict) + (while (success && i < size) { + success = fos.write(buffer(i)) + i += 1 + }) invariant { + 0 <= i && i <= size + } + + success + } + + def debugWriteBytes(buffer: Buffer): Unit = { + // require(buffer.nonEmpty) + var i = 0 - // 7. prepare for next codeword - w.set(entry) + val size = buffer.size - // 8. if more codewords available, process them - c = readCodeword(fis) + (while (i < size) { + val byte = buffer(i) + if (0x20 <= byte && byte <= 0x7e) { + StdOut.print(byte) + } else { + StdOut.print("[") + StdOut.print(byte.toInt) + StdOut.print("]") } + StdOut.print(" ") + i += 1 + }) invariant { + 0 <= i && i <= size } + } - // Add EOF marker - abort = abort || !fos.write(EOF) - !abort - } + case class CodeWord(b1: Byte, b2: Byte) // a 16-bit code word - def outputCodeword(index: Int, fos: FOS)(implicit state: leon.io.State): Boolean = { - require(fos.isOpen) - fos.write(index) && fos.write(" ") + def index2CodeWord(index: Int): CodeWord = { + require(0 <= index && index < 65536) // unsigned index + // Shift the index in the range [-32768, 32767] to make it signed + val signed = index - 32768 + // Split it into two byte components + val b2 = signed.toByte + val b1 = (signed >>> 8).toByte + CodeWord(b1, b2) } - def outputBuffer(b: Buffer, fos: FOS)(implicit state: leon.io.State): Boolean = { - require(fos.isOpen) + def codeWord2Index(cw: CodeWord): Int = { + // When building the signed integer back, make sure to understand integer + // promotion with negative numbers: we need to avoid the signe extension here. + val signed = (cw.b1 << 8) | (0xff & cw.b2) + signed + 32768 + } ensuring { res => 0 <= res && res < 65536 } - var i = 0 - var success = true - while (success && i < b.length) { - success = fos.write(b(i)) && fos.write(" ") - i = i + 1 + case class Dictionary(private val buffers: Array[Buffer], private var nextIndex: Int) { + val capacity = buffers.length + require(isValid) + + def isValid = 0 <= nextIndex && nextIndex <= capacity && capacity == DictionarySize && allValidBuffers(buffers) + + def isEmpty = nextIndex == 0 + + def nonEmpty = !isEmpty + + def isFull = nextIndex == capacity + + def nonFull = nextIndex < capacity + + def lastIndex = { + require(nonEmpty) + nextIndex - 1 } - success - } + def contains(index: Int): Boolean = { + require(0 <= index) + index < nextIndex + } - // TODO wrap dictionary into a class? + def appendTo(index: Int, buffer: Buffer): Boolean = { + require(0 <= index && contains(index)) - // Init the dictionary with the ASCII range encoding and return the next free index in the dictionary - @inline - def initDictionary(): (Array[Buffer], Int) = { - val dict = Array.fill(DICTIONARY_SIZE) { createBuffer() } + val size = buffers(index).size - var i = 0 + if (buffer.size + size <= buffer.capacity) { + assert(buffer.nonFull) - while (i <= MAX_INT_VALUE) { - dict(i).append(i) + var i = 0 + (while (i < size) { + buffer.append(buffers(index)(i)) + i += 1 + }) invariant { + 0 <= i && i <= size && + (i < size ==> buffer.nonFull) + } - i = i + 1 + true + } else false } - (dict, i) + def insert(b: Buffer): Unit = { + require(nonFull && b.nonEmpty) + + assert(lemmaSize) + assert(isValid) + assert(nonFull) + assert(nextIndex < capacity) + assert(nextIndex < DictionarySize) + assert(nextIndex + 1 <= DictionarySize) + + StdOut.print("Inserting codeword "); debugWriteBytes(b); StdOut.print(" at index "); StdOut.println(nextIndex) + + buffers(nextIndex).set(b) // FIXME TIMEOUT (?) + + assert(lemmaSize) + assert(isValid) // FIXME TIMEOUT + assert(nonFull) // FIXME TIMEOUT + assert(nextIndex < capacity) // FIXME TIMEOUT + assert(nextIndex < DictionarySize) // FIXME TIMEOUT + assert(nextIndex + 1 <= DictionarySize) // FIXME TIMEOUT + + nextIndex += 1 // FIXME TIMEOUT + } ensuring { _ => isValid } // FIXME TIMEOUT + + def encode(b: Buffer): Option[CodeWord] = { + require(b.nonEmpty) + + var found = false + var i = 0 + + (while (!found && i < nextIndex) { + if (buffers(i).isEqual(b)) { + StdOut.print("Found buffer "); debugWriteBytes(b); StdOut.print(" at index "); StdOut.print(i); StdOut.print(" :: "); debugWriteBytes(buffers(i)); StdOut.println() + found = true + } else { + i += 1 + } + }) invariant { + 0 <= i && i <= nextIndex && i <= capacity && + isValid && + (found ==> (i < nextIndex && buffers(i).isEqual(b))) + } + + if (found) Some(index2CodeWord(i)) else None() + } } - // Attempt to find `buffer` in the given `dict`. - // Return (true, idx) if found, otherwise (false, _). - def findIndex(dict: Array[Buffer], dictSize: Int, buffer: Buffer): (Boolean, Int) = { - var idx = 0 - var found = false + @inline // in order to "return" the arrays + def createDictionary() = { + Dictionary(Array.fill(DictionarySize){ createBuffer() }, 0) + } ensuring { res => res.isEmpty } - while (!found && idx < dictSize) { + def initialise(dict: Dictionary): Unit = { + require(dict.isEmpty) // initialise only fresh dictionaries - if (dict(idx).isEqual(buffer)) - found = true - else - idx = idx + 1 + val buffer = createBuffer() + assert(buffer.isEmpty) + + var value: Int = Byte.MinValue // Use an Int to avoid overflow issues + + (while (value <= Byte.MaxValue) { + buffer.append(value.toByte) // no truncation here + dict.insert(buffer) + buffer.dropLast() + value += 1 + }) invariant { // FIXME TIMEOUT + dict.nonFull && + buffer.isEmpty && + value >= Byte.MinValue && value <= Byte.MaxValue + 1 // last iteration goes "overflow" on Byte } + } ensuring { _ => dict.isValid && dict.nonEmpty } - (found, idx) - } + def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) - // Insert a given word (`buffer`) into `dict` at the given `index` and return the next index - def insertWord(index: Int, buffer: Buffer, dict: Array[Buffer]): Int = { - require(0 <= index && index < dict.length) - dict(index).set(buffer) - index + 1 + // Initialise the dictionary with the basic alphabet + val dictionary = createDictionary() + initialise(dictionary) + + // Small trick to move the static arrays outside the main encoding function; + // this helps analysing the C code in a debugger (less local variables) but + // it actually has no impact on performance (or should, in theory). + encodeImpl(dictionary, fis, fos) } - def isValidInput(x: Int) = x >= MIN_INT_VALUE && x <= MAX_INT_VALUE + def encodeImpl(dictionary: Dictionary, fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen && dictionary.nonEmpty) - def readByteAsInt(fis: FIS)(implicit state: leon.io.State): Int = { - require(fis.isOpen) - val x = fis.readInt + var bufferFull = false // TODO handle this as a non-fatal thing. + var dictFull = dictionary.isFull // TODO handle this as a non-fatal thing. + var ioError = false - // huhu, I'm clearly cheating here!!! - if (x < 0) 0 // 0 and not MAX_INT_VALUE as we need to handle EOF - else if (x > MAX_INT_VALUE) MAX_INT_VALUE - else x - } ensuring { res => res == EOF || isValidInput(res) } + val buffer = createBuffer() + assert(buffer.isEmpty && buffer.nonFull) - def readCodeword(fis: FIS)(implicit state: leon.io.State): Int = { - require(fis.isOpen) - fis.readInt + var currentOpt = tryReadNext(fis) + + /* + * def printStatus(): Unit = { + * StdOut.print("Status: ") + * if (bufferFull) StdOut.print("full") else StdOut.print(" ok ") + * if (dictFull) StdOut.print("full") else StdOut.print(" ok ") + * if (ioError) StdOut.print("xxxx") else StdOut.print(" ok ") + * StdOut.print("buffer: "); debugWriteBytes(buffer) + * StdOut.println() + * } + */ + + // printStatus() + + // Read from the input file all its content, stop when an error occurs + // (either output error or full buffer) + (while (!bufferFull && !ioError && !dictFull && currentOpt.isDefined) { + val c = currentOpt.get + + StdOut.print("Input = ["); StdOut.print(c.toInt); StdOut.println("]") + + assert(buffer.nonFull) + buffer.append(c) + assert(buffer.nonEmpty) + + StdOut.print("Current buffer: "); debugWriteBytes(buffer); StdOut.println() + + val code = dictionary.encode(buffer) + + if (code.isDefined) { + StdOut.print("Code for buffer is known: "); StdOut.println(codeWord2Index(code.get)) + } else { + StdOut.println("Code for buffer is NOT known") + } + + val processBuffer = buffer.isFull || code.isEmpty + + if (processBuffer) { + StdOut.println("Processing buffer") + // Add s (with c) into the dictionary + dictionary.insert(buffer) + + // Encode s (without c) and print it + buffer.dropLast() + assert(buffer.nonFull) + assert(buffer.nonEmpty) + val code2 = dictionary.encode(buffer) + + assert(code2.isDefined) // (*) + // To prove (*) we might need to: + // - prove the dictionary can encode any 1-length buffer + // - the buffer was empty when entering the loop or + // that the initial buffer was in the dictionary. + ioError = !writeCodeWord(fos, code2.get) + + // Prepare for next codeword: set s to c + buffer.clear() + buffer.append(c) + assert(buffer.nonEmpty) + + StdOut.print("New buffer: "); debugWriteBytes(buffer); StdOut.println() + } else { + StdOut.println("Keeping buffer as is") + } + + bufferFull = buffer.isFull + dictFull = dictionary.isFull + + currentOpt = tryReadNext(fis) + // printStatus() + StdOut.println() + }) invariant { + bufferFull == buffer.isFull && + dictFull == dictionary.isFull && + ((!bufferFull && !ioError && !dictFull) ==> buffer.nonEmpty) // it might always be true... + } + + // Process the remaining buffer + if (!bufferFull && !ioError && !dictFull) { + StdOut.print("Processing last buffer: "); debugWriteBytes(buffer); StdOut.println() + val code = dictionary.encode(buffer) + assert(code.isDefined) // See (*) above. + StdOut.print("Last code: "); StdOut.println(codeWord2Index(code.get)) + ioError = !writeCodeWord(fos, code.get) + // printStatus() + } + + !bufferFull && !ioError && !dictFull } + def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) - @inline - val SUCCESS = 0 + // Initialise the dictionary with the basic alphabet + val dictionary = createDictionary() + initialise(dictionary) - @inline - val OPEN_ERROR = -1 + decodeImpl(dictionary, fis, fos) + } - @inline - val ENCODE_ERROR = 1 + def decodeImpl(dictionary: Dictionary, fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen && dictionary.nonEmpty) - @inline - val DECODE_ERROR = 2 + var illegalInput = false + var ioError = false + var bufferFull = false - def _main() = { - implicit val state = leon.io.newState + var currentOpt = tryReadCodeWord(fis) + + val buffer = createBuffer() + if (currentOpt.isDefined) { + val cw = currentOpt.get + val index = codeWord2Index(cw) - def status(x: Int): Int = { - x match { - case x if x == SUCCESS => StdOut.println("success") - case x if x == OPEN_ERROR => StdOut.println("couldn't open file") - case x if x == ENCODE_ERROR => StdOut.println("encoding failed") - case x if x == DECODE_ERROR => StdOut.println("decoding failed") - case _ => StdOut.print("unknown error "); StdOut.println(x) + if (dictionary contains index) { + bufferFull = !dictionary.appendTo(index, buffer) + StdOut.print("Writing buffer for index "); StdOut.print(index); StdOut.print(": "); debugWriteBytes(buffer); StdOut.println() + ioError = !writeBytes(fos, buffer) + } else { + illegalInput = true + } + + currentOpt = tryReadCodeWord(fis) + } + + (while (!illegalInput && !ioError && !bufferFull && currentOpt.isDefined) { + val cw = currentOpt.get + val index = codeWord2Index(cw) + val entry = createBuffer() + + if (dictionary contains index) { + illegalInput = !dictionary.appendTo(index, entry) + } else if (index == dictionary.lastIndex + 1) { + entry.set(buffer) + entry.append(buffer(0)) + } else { + illegalInput = true } - x + StdOut.print("Writing buffer: "); debugWriteBytes(entry); StdOut.println() + ioError = !writeBytes(fos, entry) + bufferFull = buffer.isFull + + if (!bufferFull) { + val tmp = createBuffer() + tmp.set(buffer) + tmp.append(entry(0)) + dictionary.insert(tmp) + + buffer.set(entry) + } + + currentOpt = tryReadCodeWord(fis) + }) invariant { + true + } + + + !illegalInput && !ioError && !bufferFull + } + sealed abstract class Status + case class Success() extends Status + case class OpenError() extends Status + case class EncodeError() extends Status + case class DecodeError() extends Status + + implicit def status2boolean(s: Status): Boolean = s match { + case Success() => true + case _ => false + } + + def _main() = { + implicit val state = leon.io.newState + + def statusCode(s: Status): Int = s match { + case Success() => StdOut.println("success"); 0 + case OpenError() => StdOut.println("couldn't open file"); 1 + case EncodeError() => StdOut.println("encoding failed"); 2 + case DecodeError() => StdOut.println("decoding failed"); 3 } - def encodeFile(): Int = { + def encodeFile(): Status = { val input = FIS.open("input.txt") val encoded = FOS.open("encoded.txt") val res = if (input.isOpen && encoded.isOpen) { - if (encode(input, encoded)) status(SUCCESS) - else status(ENCODE_ERROR) - } else status(OPEN_ERROR) + if (encode(input, encoded)) Success() + else EncodeError() + } else OpenError() encoded.close input.close @@ -433,15 +1048,15 @@ object LZW { res } - def decodeFile(): Int = { + def decodeFile(): Status = { val encoded = FIS.open("encoded.txt") val decoded = FOS.open("decoded.txt") val res = if (encoded.isOpen && decoded.isOpen) { - if (decode(encoded, decoded)) status(SUCCESS) - else status(DECODE_ERROR) - } else status(OPEN_ERROR) + if (decode(encoded, decoded)) Success() + else DecodeError() + } else OpenError() decoded.close encoded.close @@ -450,8 +1065,7 @@ object LZW { } val r1 = encodeFile() - if (r1 == SUCCESS) decodeFile() - else r1 + statusCode(if (r1) decodeFile() else r1) } @extern From 32823085aadd396842f47a906131b984348915dd Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 9 Dec 2016 18:35:37 +0100 Subject: [PATCH 56/77] Remove debug output in LZW --- .../regression/genc/unverified/LZW.scala | 76 +------------------ 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index 8500e917e..288c53de0 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -616,25 +616,11 @@ object LZW { def tryReadNext(fis: FIS)(implicit state: leon.io.State): Option[Byte] = { require(fis.isOpen) - val b = fis.tryReadByte() - StdOut.print("Reading next byte: ") - if (b.isDefined) { - val byte = b.get - if (0x20 <= byte && byte <= 0x7e) { - StdOut.print(byte) - } else { - StdOut.print("[") - StdOut.print(byte.toInt) - StdOut.print("]") - } - } else StdOut.print("EOF") - StdOut.println() - b + fis.tryReadByte() } def writeCodeWord(fos: FOS, cw: CodeWord): Boolean = { require(fos.isOpen) - StdOut.print("Writing codeword for index "); val index = codeWord2Index(cw); StdOut.println(index) fos.write(cw.b1) && fos.write(cw.b2) } @@ -666,28 +652,6 @@ object LZW { success } - def debugWriteBytes(buffer: Buffer): Unit = { - // require(buffer.nonEmpty) - var i = 0 - - val size = buffer.size - - (while (i < size) { - val byte = buffer(i) - if (0x20 <= byte && byte <= 0x7e) { - StdOut.print(byte) - } else { - StdOut.print("[") - StdOut.print(byte.toInt) - StdOut.print("]") - } - StdOut.print(" ") - i += 1 - }) invariant { - 0 <= i && i <= size - } - } - case class CodeWord(b1: Byte, b2: Byte) // a 16-bit code word @@ -764,8 +728,6 @@ object LZW { assert(nextIndex < DictionarySize) assert(nextIndex + 1 <= DictionarySize) - StdOut.print("Inserting codeword "); debugWriteBytes(b); StdOut.print(" at index "); StdOut.println(nextIndex) - buffers(nextIndex).set(b) // FIXME TIMEOUT (?) assert(lemmaSize) @@ -786,7 +748,6 @@ object LZW { (while (!found && i < nextIndex) { if (buffers(i).isEqual(b)) { - StdOut.print("Found buffer "); debugWriteBytes(b); StdOut.print(" at index "); StdOut.print(i); StdOut.print(" :: "); debugWriteBytes(buffers(i)); StdOut.println() found = true } else { i += 1 @@ -851,44 +812,20 @@ object LZW { var currentOpt = tryReadNext(fis) - /* - * def printStatus(): Unit = { - * StdOut.print("Status: ") - * if (bufferFull) StdOut.print("full") else StdOut.print(" ok ") - * if (dictFull) StdOut.print("full") else StdOut.print(" ok ") - * if (ioError) StdOut.print("xxxx") else StdOut.print(" ok ") - * StdOut.print("buffer: "); debugWriteBytes(buffer) - * StdOut.println() - * } - */ - - // printStatus() - // Read from the input file all its content, stop when an error occurs // (either output error or full buffer) (while (!bufferFull && !ioError && !dictFull && currentOpt.isDefined) { val c = currentOpt.get - StdOut.print("Input = ["); StdOut.print(c.toInt); StdOut.println("]") - assert(buffer.nonFull) buffer.append(c) assert(buffer.nonEmpty) - StdOut.print("Current buffer: "); debugWriteBytes(buffer); StdOut.println() - val code = dictionary.encode(buffer) - if (code.isDefined) { - StdOut.print("Code for buffer is known: "); StdOut.println(codeWord2Index(code.get)) - } else { - StdOut.println("Code for buffer is NOT known") - } - val processBuffer = buffer.isFull || code.isEmpty if (processBuffer) { - StdOut.println("Processing buffer") // Add s (with c) into the dictionary dictionary.insert(buffer) @@ -909,18 +846,12 @@ object LZW { buffer.clear() buffer.append(c) assert(buffer.nonEmpty) - - StdOut.print("New buffer: "); debugWriteBytes(buffer); StdOut.println() - } else { - StdOut.println("Keeping buffer as is") } bufferFull = buffer.isFull dictFull = dictionary.isFull currentOpt = tryReadNext(fis) - // printStatus() - StdOut.println() }) invariant { bufferFull == buffer.isFull && dictFull == dictionary.isFull && @@ -929,12 +860,9 @@ object LZW { // Process the remaining buffer if (!bufferFull && !ioError && !dictFull) { - StdOut.print("Processing last buffer: "); debugWriteBytes(buffer); StdOut.println() val code = dictionary.encode(buffer) assert(code.isDefined) // See (*) above. - StdOut.print("Last code: "); StdOut.println(codeWord2Index(code.get)) ioError = !writeCodeWord(fos, code.get) - // printStatus() } !bufferFull && !ioError && !dictFull @@ -967,7 +895,6 @@ object LZW { if (dictionary contains index) { bufferFull = !dictionary.appendTo(index, buffer) - StdOut.print("Writing buffer for index "); StdOut.print(index); StdOut.print(": "); debugWriteBytes(buffer); StdOut.println() ioError = !writeBytes(fos, buffer) } else { illegalInput = true @@ -990,7 +917,6 @@ object LZW { illegalInput = true } - StdOut.print("Writing buffer: "); debugWriteBytes(entry); StdOut.println() ioError = !writeBytes(fos, entry) bufferFull = buffer.isFull From bedc207dfe6bdb9567716c168fd29a2c490c5cd4 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 9 Dec 2016 18:36:23 +0100 Subject: [PATCH 57/77] Make LZW continue with full dictionary --- .../regression/genc/unverified/LZW.scala | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index 288c53de0..ba837ecaa 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -804,7 +804,6 @@ object LZW { require(fis.isOpen && fos.isOpen && dictionary.nonEmpty) var bufferFull = false // TODO handle this as a non-fatal thing. - var dictFull = dictionary.isFull // TODO handle this as a non-fatal thing. var ioError = false val buffer = createBuffer() @@ -814,7 +813,7 @@ object LZW { // Read from the input file all its content, stop when an error occurs // (either output error or full buffer) - (while (!bufferFull && !ioError && !dictFull && currentOpt.isDefined) { + (while (!bufferFull && !ioError && currentOpt.isDefined) { val c = currentOpt.get assert(buffer.nonFull) @@ -826,8 +825,10 @@ object LZW { val processBuffer = buffer.isFull || code.isEmpty if (processBuffer) { - // Add s (with c) into the dictionary - dictionary.insert(buffer) + // Add s (with c) into the dictionary, if the dictionary size limitation allows it + if (dictionary.nonFull) { + dictionary.insert(buffer) + } // Encode s (without c) and print it buffer.dropLast() @@ -849,23 +850,21 @@ object LZW { } bufferFull = buffer.isFull - dictFull = dictionary.isFull currentOpt = tryReadNext(fis) }) invariant { bufferFull == buffer.isFull && - dictFull == dictionary.isFull && - ((!bufferFull && !ioError && !dictFull) ==> buffer.nonEmpty) // it might always be true... + ((!bufferFull && !ioError) ==> buffer.nonEmpty) // it might always be true... } // Process the remaining buffer - if (!bufferFull && !ioError && !dictFull) { + if (!bufferFull && !ioError) { val code = dictionary.encode(buffer) assert(code.isDefined) // See (*) above. ioError = !writeCodeWord(fos, code.get) } - !bufferFull && !ioError && !dictFull + !bufferFull && !ioError } def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { @@ -924,7 +923,9 @@ object LZW { val tmp = createBuffer() tmp.set(buffer) tmp.append(entry(0)) - dictionary.insert(tmp) + if (dictionary.nonFull) { + dictionary.insert(tmp) + } buffer.set(entry) } From fc1236d70565445dad1069ec5409b32ce5b41f5d Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 9 Dec 2016 19:11:51 +0100 Subject: [PATCH 58/77] Remove old code from LZW --- .../regression/genc/unverified/LZW.scala | 318 +----------------- 1 file changed, 2 insertions(+), 316 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index ba837ecaa..fe00e75b0 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -18,8 +18,7 @@ object LZW { // Encoding using fixed size of word; // Input alphabet is the ASCII range (0-255); // A word is made of 16 bits (instead of the classic 12-bit scenario, for simplicity); - // The dictionary is an array of ??? where the index is the key; - // TODO s/index/codeword -> better yet: define index2codeword conversion (Int -> (Byte, Byte)) + // The dictionary is an array of Buffer where the index is the key; // TODO 16-bit integers would be useful here! // We limit the size of the dictionary to 2^10 @@ -166,7 +165,6 @@ object LZW { !isRangeEqual(a, b, 0, 0) }.holds - private def testBufferIsEqual(): Boolean = { val a = createBuffer() val b = createBuffer() @@ -182,6 +180,7 @@ object LZW { !a.isEqual(b) }.holds + // Helper for range equality checking private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) @@ -295,324 +294,13 @@ object LZW { assert(isEqual(b)) } ensuring { _ => b.isValid && isValid && nonEmpty && isEqual(b) /* && b.isEqual(old(b)) */ } -/* - * def set(b: Buffer): Unit = { - * require(array.length == b.length) // all buffers have the same capacity - * - * var i = 0 - * (while (i < b.length) { - * array(i) = b.array(i) - * i = i + 1 - * }) invariant { - * i >= 0 && i <= b.length && - * array.length >= b.length && - * isRangeEqual(array, b.array, 0, i) - * } - * - * length = b.length - * } ensuring { _ => this.isEqual(b) } - */ - } - /* - * private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { - * require(0 <= from && from <= to && to < a.length && a.length == b.length) - * if (from == to) true else { - * // a(from) == b(from) && (from <= to ==> isRangeEqual(a, b, from + 1, to)) - * } - * } - */ - @inline // very important because we cannot return arrays def createBuffer(): Buffer = { Buffer(Array.fill(BufferSize)(0), 0) } ensuring { b => b.isEmpty && b.nonFull && b.isValid } - // Read the given input file `fis`, encode its content, save the encoded - // version into `fos`, and return true on success. - // - // The algorithm is: - // 0a. init the dictionary to support the full input alphabet - // 0b. set P to empty (P below is our `buffer`) - // 1. read the next character C (a.k.a. `current`) - // 2. append C to P (i.e. "P = P :: C") - // 3. check if the dictionary has an entry for the current buffer P - // 4. if yes, keep the buffer P as is (i.e. with C at the end) - // 5a. if not, remove C from the buffer P, - // 5b. output the codeword associated with P - // 5c. insert "P :: C" into the dictionary to create a new codeword - // 5d. set P to the monocharacter string C - // 6. goto 1 if more characters available - // 7. P is non empty! So find the corresponding codeword and output it -/* - * def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { - * require(fis.isOpen && fos.isOpen) - * - * // 0a. Init dictionary with all basic range - * val (dict, idx) = initDictionary() - * - * encodeImpl(fis, fos, dict, idx) - * } - */ - -/* - * def encodeImpl(fis: FIS, fos: FOS, dict: Array[Buffer], idx: Int)(implicit state: leon.io.State): Boolean = { - * require(fis.isOpen && fos.isOpen && dict.length == DICTIONARY_SIZE && 0 <= idx && idx <= dict.length && allValidBuffer(dict)) - * - * var nextIndex = idx - * - * // 0b. Create an empty buffer - * val buffer = createBuffer() - * - * // 1. Read the next input - * var current = readByte(fis) - * - * // We cannot use `return` to shortcircuit the function and do an early exit, - * // so we keep an abort boolean and use if-statements to wrap actions. - * var abort = false - * - * ( - * while (!abort && current.isDefined) { - * // 2. Append input to the buffer - * buffer.append(current.get) - * - * // 3. Check if the entry is known - * assert(dict.length == DICTIONARY_SIZE) - * val index = findIndex(dict, nextIndex, buffer) - * - * if (index.isEmpty) { - * // 5a. Restore previous buffer - * buffer.dropLast() - * - * // 5b. Output the current codeword - * val index2 = findIndex(dict, nextIndex, buffer) - * // By construction, the index will be found but this fact is not proven so we have to check for it - * if (index2.isEmpty || !outputCodeword(index2.get, fos)) - * abort = true - * - * // 5c. Insert the extended codeword in the dictionary - * buffer.append(current.get) - * nextIndex = insertWord(nextIndex, buffer, dict) - * - * // 5d. Prepare buffer for next round - * buffer.clear() - * buffer.append(current.get) - * } - * // else: 4. Noop - * - * // Gracefully exit if we cannot encode the next byte - * if (buffer.isFull || nextIndex == dict.length) - * abort = true - * - * // 6. Read next char for next while loop iteration - * current = readByte(fis) - * } - * ) invariant { - * (!abort ==> (buffer.notFull && nextIndex < dict.length)) && - * dict.length == DICTIONARY_SIZE && - * 0 <= nextIndex && nextIndex <= dict.length && - * allValidBuffer(dict) - * } - * - * // 7. Process the remaining buffer, if any - * if (!abort && buffer.nonEmpty) { - * assert(dict.length == DICTIONARY_SIZE) - * val index = findIndex(dict, nextIndex, buffer) - * // Either at step 3 the buffer was: - * // - found in the dictionary and the buffer was not altered (we just read EOF), or - * // - not found in the dictionary and therefore the buffer now contains only one character. - * assert(index.isDefined) - * - * if (!outputCodeword(index.get, fos)) - * abort = true - * } - * - * !abort - * } - */ - - // Read the given input file `fis`, decode its content, save the decoded - // version into `fos`, and return true on success. - // - // The algorithm is: - // 0a. init the dictionary to support the full input alphabet - // 0b. create a buffer, w - // 1. read the next codeword c - // 2a. if not EOF: - // 2b. append c to w - // 2c. output c - // 2d. read the next codeword c - // 3. if EOF stop - // 4a. if c is in dictionary (i.e. c < |dict|), set entry to the corresponding value - // 4b. else if c == |dict|, set entry to w, then append the first character of w to entry - // 4c. else abort (decoding error) - // 5. output entry - // 6a. set tmp to w and then append the first character of entry to tmp - // 6b. add tmp at the end of the dictionary - // 7. set w to entry - // 8. goto 4 if more codewords available -/* - * def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { - * require(fis.isOpen && fos.isOpen) - * - * // 0a. Init dictionary with all basic range - * val (dict, idx) = initDictionary() - * var nextIndex = idx - * - * // 0b. Create an empty buffer - * val w = createBuffer() - * - * // 1. Read the next input - * // TODO we need to be able to read Byte! - * var c = readCodeword(fis) - * - * // We cannot use `return` to shortcircuit the function and do an early exit, - * // so we keep an abort boolean and use if-statements to wrap actions. - * var abort = false - * - * // 2a-2d: process the first codeword - * if (c != EOF) { - * w.append(c) - * abort = !outputBuffer(w, fos) - * c = readCodeword(fis) - * } - * - * // Process the remaining codewords - * while (!abort && c != EOF) { - * // 4a-4c: process current codeword - * val entry = createBuffer() - * if (c < nextIndex) { - * entry.set(dict(c)) - * } else if (c == nextIndex) { - * entry.set(w) - * entry.append(w(0)) - * } else { - * abort = true - * } - * - * // Make sure we haven't used the full buffer w or we won't be able to append something; - * // Gracefully exit if we cannot decode the next codeword; - * // 5. output the current entry - * abort = abort || w.isFull || nextIndex == dict.length || !outputBuffer(entry, fos) - * - * if (!abort) { - * // 6a-6b. augment the dictionary - * val tmp = createBuffer() - * tmp.set(w) - * tmp.append(entry(0)) - * nextIndex = insertWord(nextIndex, tmp, dict) - * - * // 7. prepare for next codeword - * w.set(entry) - * - * // 8. if more codewords available, process them - * c = readCodeword(fis) - * } - * } - * - * // Add EOF marker - * abort = abort || !fos.write(EOF) - * - * !abort - * } - */ - - /* - * def outputCodeword(index: Int, fos: FOS)(implicit state: leon.io.State): Boolean = { - * require(fos.isOpen) - * val b2 = index.toByte - * val b1 = (index >>> 8).toByte - * fos.write(b1) && fos.write(b2) - * } - */ - -/* - * def outputBuffer(b: Buffer, fos: FOS)(implicit state: leon.io.State): Boolean = { - * require(fos.isOpen) - * - * var i = 0 - * var success = true - * - * while (success && i < b.length) { - * success = fos.write(b(i)) && fos.write(" ") - * i = i + 1 - * } - * - * success - * } - */ - - // TODO wrap dictionary into a class? - - // Init the dictionary with the range of Byte and return the next free index in the dictionary -/* - * @inline - * def initDictionary(): (Array[Buffer], Int) = { - * val dict = Array.fill(DICTIONARY_SIZE) { createBuffer() } - * assert(dict.length == DICTIONARY_SIZE) - * assert(allValidBuffer(dict)) - * // assert(arrayForall(dict, { buffer: Buffer => buffer.isEmpty && buffer.notFull })) // not supported??? - * - * var index = 0 - * var value: Int = Byte.MinValue // Use an Int to avoid overflow issues - * - * (while (value <= Byte.MaxValue) { - * assert(Byte.MinValue <= value && value <= Byte.MaxValue) - * assert(dict(index).notFull) // this fails for some reason - * dict(index).append(value.toByte) // safe conversion, no loss of information - * - * index += 1 - * value += 1 // last iteration would overflow on Byte but not on Int - * }) invariant { - * value >= Byte.MinValue && value <= Byte.MaxValue + 1 && - * index >= 0 && index <= DICTIONARY_SIZE && - * index == value + -Byte.MinValue && // they increment at the same speed - * dict.length == DICTIONARY_SIZE && - * allValidBuffer(dict) - * } - * - * (dict, index) - * } ensuring { res => allValidBuffer(res._1) && res._2 == ALPHABET_SIZE } - */ - - // Attempt to find `buffer` in the given `dict`. -/* - * def findIndex(dict: Array[Buffer], dictSize: Int, buffer: Buffer): Option[Int] = { - * require(dict.length == DICTIONARY_SIZE && 0 <= dictSize && dictSize <= dict.length && allValidBuffer(dict)) - * - * var idx = 0 - * var found = false - * - * (while (!found && idx < dictSize) { - * found = dict(idx).isEqual(buffer) - * // idx = idx + 1 // buggy!!! increement only when not found!, counter example was found!!! - * if (!found) - * idx += 1 - * }) ensuring { - * idx >= 0 && idx <= dictSize - * } - * - * if (found) Some[Int](idx) else None[Int]() - * } ensuring { res => - * // (res.isDefined == arrayExists(dict, 0, dictSize - 1, { elem: Buffer => elem.isEqual(buffer) })) && // Not supported??? - * (res.isDefined ==> dict(res.get).isEqual(buffer)) && - * dict.length == DICTIONARY_SIZE - * } - * - */ - // Insert a given word (`buffer`) into `dict` at the given `index` and return the next index -/* - * def insertWord(index: Int, buffer: Buffer, dict: Array[Buffer]): Int = { - * require(0 <= index && index < dict.length && dict.length == DICTIONARY_SIZE && allValidBuffer(dict)) - * - * dict(index).set(buffer) - * index + 1 - * } ensuring { res => - * 0 < res && res <= dict.length && - * dict.length == DICTIONARY_SIZE - * } - */ def tryReadNext(fis: FIS)(implicit state: leon.io.State): Option[Byte] = { require(fis.isOpen) @@ -652,7 +340,6 @@ object LZW { success } - case class CodeWord(b1: Byte, b2: Byte) // a 16-bit code word def index2CodeWord(index: Int): CodeWord = { @@ -935,7 +622,6 @@ object LZW { true } - !illegalInput && !ioError && !bufferFull } sealed abstract class Status From 38f1c76ef48e815aeb41803976e73ca5a9f39aaf Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 10:48:27 +0100 Subject: [PATCH 59/77] Avoid overflow in LZW --- .../resources/regression/genc/unverified/LZW.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index fe00e75b0..8f85f0516 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -35,7 +35,7 @@ object LZW { DictionarySize >= AlphabetSize && BufferSize > 0 && AlphabetSize > 0 && - DictionarySize <= 1000000 + DictionarySize <= 65536 // Cannot encode more index using only 16-bit codewords }.holds private def lemmaBufferFull(b: Buffer): Boolean = { @@ -224,7 +224,9 @@ object LZW { else { isEmpty || isRangeEqual(array, b.array, 0, length - 1) } } //ensuring { _ => this.isEqual(old(this)) && b.isEqual(old(b)) } // this VC does infinite recursion - def size = length + def size = { + length + } ensuring { res => 0 <= res && res <= capacity } def apply(index: Int): Byte = { require(index >= 0 && index < length) @@ -377,7 +379,7 @@ object LZW { def lastIndex = { require(nonEmpty) nextIndex - 1 - } + } ensuring { res => 0 <= res && res < capacity } def contains(index: Int): Boolean = { require(0 <= index) @@ -389,7 +391,8 @@ object LZW { val size = buffers(index).size - if (buffer.size + size <= buffer.capacity) { + assert(buffer.capacity == BufferSize) + if (buffer.size < buffer.capacity - size) { assert(buffer.nonFull) var i = 0 @@ -619,7 +622,7 @@ object LZW { currentOpt = tryReadCodeWord(fis) }) invariant { - true + dictionary.nonEmpty } !illegalInput && !ioError && !bufferFull From b6875274a21c64cc9a2b71d7738d7ac865f093be Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 15:14:53 +0100 Subject: [PATCH 60/77] Update LZW parameters --- src/test/resources/regression/genc/unverified/LZW.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index 8f85f0516..d38db2039 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -19,15 +19,14 @@ object LZW { // Input alphabet is the ASCII range (0-255); // A word is made of 16 bits (instead of the classic 12-bit scenario, for simplicity); // The dictionary is an array of Buffer where the index is the key; - // TODO 16-bit integers would be useful here! - // We limit the size of the dictionary to 2^10 + // We limit the size of the dictionary to an arbitrary size, less than or equals to 2^16. @inline - val DictionarySize = 1024 + val DictionarySize = 4096 // We use fix-sized buffers @inline - val BufferSize = 10 // characters + val BufferSize = 64 // characters val AlphabetSize = Byte.MaxValue + -Byte.MinValue From 34ff8eec76226c474e2e0679df952bff5c3b42d3 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 18:33:30 +0100 Subject: [PATCH 61/77] Rename function --- .../regression/genc/unverified/LZW.scala | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index d38db2039..f1f8a4018 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -73,74 +73,74 @@ object LZW { d.isEmpty ==> d.nonFull }.holds - private def lemmaSelfRangeEqual(a: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaSelfRangesEqual(a: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length) - isRangeEqual(a, a, from, to) because { - if (from == to) check { lemmaUnitRangeEqual(a, a, from) } + areRangesEqual(a, a, from, to) because { + if (from == to) check { lemmaUnitRangesEqual(a, a, from) } else { check { a(from) == a(from) } && check { a(to) == a(to) } && - check { lemmaSelfRangeEqual(a, from, to - 1) } && - check { lemmaSelfRangeEqual(a, from + 1, to) } + check { lemmaSelfRangesEqual(a, from, to - 1) } && + check { lemmaSelfRangesEqual(a, from + 1, to) } } } }.holds - private def lemmaRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaAreRangesEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) - ( isRangeEqual(a, b, from, to) ==> isRangeEqual(b, a, from, to) ) // FIXME timeout + ( areRangesEqual(a, b, from, to) ==> areRangesEqual(b, a, from, to) ) // FIXME timeout }.holds - private def lemmaSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaSubRangesEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) - isRangeEqual(a, b, from, to) ==> ( + areRangesEqual(a, b, from, to) ==> ( check { a(from) == b(from) } && - check { (from + 1 <= to) ==> isRangeEqual(a, b, from + 1, to) } + check { (from + 1 <= to) ==> areRangesEqual(a, b, from + 1, to) } ) }.holds - private def lemmaSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaSubRangesEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) - isRangeEqual(a, b, from, to) ==> ( - check { (a(to) == b(to)) because lemmaRangeEndEqual(a, b, from, to) } && - check { (from <= to - 1) ==> isRangeEqual(a, b, from, to - 1) } // FIXME timeout + areRangesEqual(a, b, from, to) ==> ( + check { (a(to) == b(to)) because lemmaRangesEndEqual(a, b, from, to) } && + check { (from <= to - 1) ==> areRangesEqual(a, b, from, to - 1) } // FIXME timeout ) }.holds // FIXME TIMEOUT - private def lemmaMiniSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaMiniSubRangesEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) - isRangeEqual(a, b, from, to) ==> ( - isRangeEqual(a, b, from, from) because a(from) == b(from) + areRangesEqual(a, b, from, to) ==> ( + areRangesEqual(a, b, from, from) because a(from) == b(from) ) }.holds - private def lemmaMiniSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def lemmaMiniSubRangesEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) - isRangeEqual(a, b, from, to) ==> ( - isRangeEqual(a, b, to, to) because { + areRangesEqual(a, b, from, to) ==> ( + areRangesEqual(a, b, to, to) because { if (from == to) check { a(to) == b(to) } else { check { from < to } && - check { lemmaMiniSubRangeEqual2(a, b, from + 1, to) } && - check { lemmaMiniSubRangeEqual2(a, b, from, to - 1) } + check { lemmaMiniSubRangesEqual2(a, b, from + 1, to) } && + check { lemmaMiniSubRangesEqual2(a, b, from, to - 1) } } } ) }.holds - private def lemmaUnitRangeEqual(a: Array[Byte], b: Array[Byte], pos: Int): Boolean = { + private def lemmaUnitRangesEqual(a: Array[Byte], b: Array[Byte], pos: Int): Boolean = { require(0 <= pos && pos < a.length && pos < b.length) - isRangeEqual(a, b, pos, pos) ==> (a(pos) == b(pos)) + areRangesEqual(a, b, pos, pos) ==> (a(pos) == b(pos)) }.holds - private def lemmaRangeEndEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { - require(0 <= from && from <= to && to < a.length && to < b.length && isRangeEqual(a, b, from, to)) + private def lemmaRangesEndEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length && areRangesEqual(a, b, from, to)) ( a(to) == b(to) ) because { if (from == to) trivial else { check { from < to } && - check { lemmaRangeEndEqual(a, b, from + 1, to) } + check { lemmaRangesEndEqual(a, b, from + 1, to) } } } }.holds @@ -158,10 +158,10 @@ object LZW { - private def testIsRangeEqual(): Boolean = { + private def testAreRangesEqual(): Boolean = { val a = Array[Byte](1, 0, 0, 0, 0, 0, 0, 0, 0, 0) val b = Array[Byte](-128, 0, 0, 0, 0, 0, 0, 0, 0, 0) - !isRangeEqual(a, b, 0, 0) + !areRangesEqual(a, b, 0, 0) }.holds private def testBufferIsEqual(): Boolean = { @@ -181,11 +181,11 @@ object LZW { // Helper for range equality checking - private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + private def areRangesEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { require(0 <= from && from <= to && to < a.length && to < b.length) a(from) == b(from) && { if (from == to) true - else isRangeEqual(a, b, from + 1, to) + else areRangesEqual(a, b, from + 1, to) } } @@ -220,7 +220,7 @@ object LZW { def isEqual(b: Buffer): Boolean = { if (b.length != length) false - else { isEmpty || isRangeEqual(array, b.array, 0, length - 1) } + else { isEmpty || areRangesEqual(array, b.array, 0, length - 1) } } //ensuring { _ => this.isEqual(old(this)) && b.isEqual(old(b)) } // this VC does infinite recursion def size = { @@ -284,7 +284,7 @@ object LZW { // lengthCheckpoint == b.length && lengthCheckpoint == length && // no mutation of the length isValid && nonEmpty && length == b.length && - (i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) // avoid OutOfBoundAccess + (i > 0 ==> areRangesEqual(array, b.array, 0, i - 1)) // avoid OutOfBoundAccess } assert(b.isValid) From b98422b5387101185cda835373366efdba6367f6 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 18:35:22 +0100 Subject: [PATCH 62/77] Use a different representation for Dictionary in LZW This representation is less efficient in terms of speed of the produced code, but requires far less small arrays and therefore significantly improve the performance of GenC and C compilers to compile the code. Also, less properties are proven on this implementation. --- .../regression/genc/unverified/LZW.scala | 189 ++++++++++++------ 1 file changed, 133 insertions(+), 56 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZW.scala index f1f8a4018..775787c6c 100644 --- a/src/test/resources/regression/genc/unverified/LZW.scala +++ b/src/test/resources/regression/genc/unverified/LZW.scala @@ -22,7 +22,10 @@ object LZW { // We limit the size of the dictionary to an arbitrary size, less than or equals to 2^16. @inline - val DictionarySize = 4096 + val DictionarySize = 8192 // number of buffers in the dictionary + + @inline + val DictionaryMemorySize = 524288 // DictionarySize * BufferSize // We use fix-sized buffers @inline @@ -200,6 +203,20 @@ object LZW { rec(0) } + def allInRange(xs: Array[Int], min: Int, max: Int): Boolean = { + require(min <= max) + + def rec(index: Int): Boolean = { + require(0 <= index && index <= xs.length) + if (xs.length == index) true + else { + min <= xs(index) && xs(index) <= max && rec(index + 1) + } + } + + rec(0) + } + // A buffer representation using a fix-sized array for memory. // @@ -223,6 +240,26 @@ object LZW { else { isEmpty || areRangesEqual(array, b.array, 0, length - 1) } } //ensuring { _ => this.isEqual(old(this)) && b.isEqual(old(b)) } // this VC does infinite recursion + def isRangeEqual(other: Array[Byte], otherStart: Int, otherSize: Int): Boolean = { + require(0 <= otherStart && 0 <= otherSize && otherSize <= other.length && otherStart <= other.length - otherSize) + if (size != otherSize) false + else if (isEmpty) true + else { + var i = 0 + var equal = true + + (while (equal && i < size) { + equal = (other(otherStart + i) == array(i)) + i += 1 + }) invariant ( + 0 <= i && i <= size && + otherStart + i <= other.length + ) + + equal + } + } + def size = { length } ensuring { res => 0 <= res && res <= capacity } @@ -260,25 +297,31 @@ object LZW { length = b.length - assert(b.isValid) - assert(isValid) - assert(nonEmpty) - assert(length == b.length) + /* + * assert(b.isValid) + * assert(isValid) + * assert(nonEmpty) + * assert(length == b.length) + */ var i = 0 (while (i < length) { - assert(isValid) - assert(nonEmpty) - assert(length == b.length) - assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) + /* + * assert(isValid) + * assert(nonEmpty) + * assert(length == b.length) + * assert(i > 0 ==> areRangesEqual(array, b.array, 0, i - 1)) + */ array(i) = b.array(i) i += 1 - assert(isValid) - assert(nonEmpty) - assert(length == b.length) - assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) + /* + * assert(isValid) + * assert(nonEmpty) + * assert(length == b.length) + * assert(i > 0 ==> areRangesEqual(array, b.array, 0, i - 1)) + */ }) invariant { // FIXME TIMEOUT 0 <= i && i <= length && // lengthCheckpoint == b.length && lengthCheckpoint == length && // no mutation of the length @@ -287,12 +330,14 @@ object LZW { (i > 0 ==> areRangesEqual(array, b.array, 0, i - 1)) // avoid OutOfBoundAccess } - assert(b.isValid) - assert(isValid) - assert(nonEmpty) - assert(isRangeEqual(array, b.array, 0, length - 1)) - assert(length == b.length) - assert(isEqual(b)) + /* + * assert(b.isValid) + * assert(isValid) + * assert(nonEmpty) + * assert(areRangesEqual(array, b.array, 0, length - 1)) + * assert(length == b.length) + * assert(isEqual(b)) + */ } ensuring { _ => b.isValid && isValid && nonEmpty && isEqual(b) /* && b.isEqual(old(b)) */ } } @@ -361,19 +406,49 @@ object LZW { } ensuring { res => 0 <= res && res < 65536 } - case class Dictionary(private val buffers: Array[Buffer], private var nextIndex: Int) { - val capacity = buffers.length - require(isValid) - - def isValid = 0 <= nextIndex && nextIndex <= capacity && capacity == DictionarySize && allValidBuffers(buffers) + case class Dictionary(private val memory: Array[Byte], private val pteps: Array[Int], private var nextIndex: Int) { + // NOTE `pteps` stands for Past The End PointerS. It holds the address in `memory` for the next buffer. + // + // By construction, for any index > 0, the begining of the buffer is stored in pteps[index - 1]. + // + // It therefore holds that the length of the buffer at the given `index` is pteps[index] - pteps[index - 1] + // for index > 0, and pteps[0] for index == 0. + + val capacity = pteps.length + require( + capacity == DictionarySize && + memory.length == DictionaryMemorySize && + allInRange(pteps, 0, DictionaryMemorySize) && + 0 <= nextIndex && nextIndex <= capacity + ) def isEmpty = nextIndex == 0 def nonEmpty = !isEmpty - def isFull = nextIndex == capacity + def isFull = !nonFull + + def nonFull = { + nextIndex < capacity && (nextIndex == 0 || (memory.length - pteps(nextIndex - 1) >= BufferSize)) + } + + private def getBufferBeginning(index: Int): Int = { + require(0 <= index && contains(index)) + if (index == 0) 0 + else pteps(index - 1) + } ensuring { res => 0 <= res && res < DictionaryMemorySize } - def nonFull = nextIndex < capacity + private def getNextBufferBeginning(): Int = { + require(nonFull) // less equirements than getBufferBeginning + if (nextIndex == 0) 0 + else pteps(nextIndex - 1) + } ensuring { res => 0 <= res && res < DictionaryMemorySize } + + private def getBufferSize(index: Int): Int = { + require(0 <= index && contains(index)) + if (index == 0) pteps(0) + else pteps(index) - pteps(index - 1) + } ensuring { res => 0 <= res && res <= BufferSize } def lastIndex = { require(nonEmpty) @@ -388,7 +463,8 @@ object LZW { def appendTo(index: Int, buffer: Buffer): Boolean = { require(0 <= index && contains(index)) - val size = buffers(index).size + val size = getBufferSize(index) + val start = getBufferBeginning(index) assert(buffer.capacity == BufferSize) if (buffer.size < buffer.capacity - size) { @@ -396,12 +472,13 @@ object LZW { var i = 0 (while (i < size) { - buffer.append(buffers(index)(i)) + buffer.append(memory(start + i)) i += 1 - }) invariant { + }) invariant ( 0 <= i && i <= size && + 0 <= start && start < DictionaryMemorySize && (i < size ==> buffer.nonFull) - } + ) true } else false @@ -410,52 +487,50 @@ object LZW { def insert(b: Buffer): Unit = { require(nonFull && b.nonEmpty) - assert(lemmaSize) - assert(isValid) - assert(nonFull) - assert(nextIndex < capacity) - assert(nextIndex < DictionarySize) - assert(nextIndex + 1 <= DictionarySize) + val start = getNextBufferBeginning() - buffers(nextIndex).set(b) // FIXME TIMEOUT (?) + var i = 0 + (while (i < b.size) { + memory(start + i) = b(i) + i += 1 + }) invariant ( + 0 <= i && i <= b.size && + 0 <= start && start < DictionaryMemorySize + ) - assert(lemmaSize) - assert(isValid) // FIXME TIMEOUT - assert(nonFull) // FIXME TIMEOUT - assert(nextIndex < capacity) // FIXME TIMEOUT - assert(nextIndex < DictionarySize) // FIXME TIMEOUT - assert(nextIndex + 1 <= DictionarySize) // FIXME TIMEOUT + pteps(nextIndex) = start + i - nextIndex += 1 // FIXME TIMEOUT - } ensuring { _ => isValid } // FIXME TIMEOUT + nextIndex += 1 + } def encode(b: Buffer): Option[CodeWord] = { require(b.nonEmpty) var found = false - var i = 0 + var index = 0 + + while (!found && index < nextIndex) { + val start = getBufferBeginning(index) + val size = getBufferSize(index) - (while (!found && i < nextIndex) { - if (buffers(i).isEqual(b)) { + if (b.isRangeEqual(memory, start, size)) { found = true } else { - i += 1 + index += 1 } - }) invariant { - 0 <= i && i <= nextIndex && i <= capacity && - isValid && - (found ==> (i < nextIndex && buffers(i).isEqual(b))) } - if (found) Some(index2CodeWord(i)) else None() + if (found) Some(index2CodeWord(index)) else None() } } @inline // in order to "return" the arrays def createDictionary() = { - Dictionary(Array.fill(DictionarySize){ createBuffer() }, 0) + // Dictionary(Array.fill(DictionarySize){ createBuffer() }, 0) + Dictionary(Array.fill(DictionaryMemorySize)(0), Array.fill(DictionarySize)(0), 0) } ensuring { res => res.isEmpty } + def initialise(dict: Dictionary): Unit = { require(dict.isEmpty) // initialise only fresh dictionaries @@ -474,7 +549,7 @@ object LZW { buffer.isEmpty && value >= Byte.MinValue && value <= Byte.MaxValue + 1 // last iteration goes "overflow" on Byte } - } ensuring { _ => dict.isValid && dict.nonEmpty } + } ensuring { _ => /*dict.isValid &&*/ dict.nonEmpty } def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { require(fis.isOpen && fos.isOpen) @@ -626,6 +701,8 @@ object LZW { !illegalInput && !ioError && !bufferFull } + + sealed abstract class Status case class Success() extends Status case class OpenError() extends Status From ec5db50d68ada4f8916385180f1b0cec55cdbac6 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 12 Dec 2016 18:39:03 +0100 Subject: [PATCH 63/77] Keep both versions of LZW around --- .../regression/genc/unverified/LZWa.scala | 690 ++++++++++++++++++ .../genc/unverified/{LZW.scala => LZWb.scala} | 0 2 files changed, 690 insertions(+) create mode 100644 src/test/resources/regression/genc/unverified/LZWa.scala rename src/test/resources/regression/genc/unverified/{LZW.scala => LZWb.scala} (100%) diff --git a/src/test/resources/regression/genc/unverified/LZWa.scala b/src/test/resources/regression/genc/unverified/LZWa.scala new file mode 100644 index 000000000..d38db2039 --- /dev/null +++ b/src/test/resources/regression/genc/unverified/LZWa.scala @@ -0,0 +1,690 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.lang._ +import leon.proof._ +import leon.annotation._ + +import leon.io.{ + FileInputStream => FIS, + FileOutputStream => FOS, + StdOut +} + +object LZW { + + // GENERAL NOTES + // ============= + // + // Encoding using fixed size of word; + // Input alphabet is the ASCII range (0-255); + // A word is made of 16 bits (instead of the classic 12-bit scenario, for simplicity); + // The dictionary is an array of Buffer where the index is the key; + + // We limit the size of the dictionary to an arbitrary size, less than or equals to 2^16. + @inline + val DictionarySize = 4096 + + // We use fix-sized buffers + @inline + val BufferSize = 64 // characters + + val AlphabetSize = Byte.MaxValue + -Byte.MinValue + + private def lemmaSize: Boolean = { + DictionarySize >= AlphabetSize && + BufferSize > 0 && + AlphabetSize > 0 && + DictionarySize <= 65536 // Cannot encode more index using only 16-bit codewords + }.holds + + private def lemmaBufferFull(b: Buffer): Boolean = { + b.isFull == !b.nonFull + }.holds + + private def lemmaBufferEmpty(b: Buffer): Boolean = { + b.isEmpty == !b.nonEmpty + }.holds + + private def lemmaBufferFullEmpty1(b: Buffer): Boolean = { + b.isFull ==> b.nonEmpty + }.holds + + private def lemmaBufferFullEmpty2(b: Buffer): Boolean = { + (BufferSize > 0 && b.isEmpty) ==> b.nonFull + }.holds + + private def lemmaBufferSelfEqual(b: Buffer): Boolean = { + b.isEqual(b) + }.holds + + private def lemmaBufferEqual(a: Buffer, b: Buffer): Boolean = { + b.isEqual(a) ==> a.isEqual(b) + }.holds + + private def lemmaBufferEqualImmutable(a: Buffer, b: Buffer): Unit = { + a.isEqual(b) + } ensuring { _ => old(a).isEqual(a) && old(b).isEqual(b) } + + private def lemmaDictionaryFull(d: Dictionary): Boolean = { + d.isFull == !d.nonFull + }.holds + + private def lemmaDictionaryFullEmpty(d: Dictionary): Boolean = { + d.isEmpty ==> d.nonFull + }.holds + + private def lemmaSelfRangeEqual(a: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length) + isRangeEqual(a, a, from, to) because { + if (from == to) check { lemmaUnitRangeEqual(a, a, from) } + else { + check { a(from) == a(from) } && + check { a(to) == a(to) } && + check { lemmaSelfRangeEqual(a, from, to - 1) } && + check { lemmaSelfRangeEqual(a, from + 1, to) } + } + } + }.holds + + private def lemmaRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + + ( isRangeEqual(a, b, from, to) ==> isRangeEqual(b, a, from, to) ) // FIXME timeout + }.holds + + private def lemmaSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + check { a(from) == b(from) } && + check { (from + 1 <= to) ==> isRangeEqual(a, b, from + 1, to) } + ) + }.holds + + private def lemmaSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + check { (a(to) == b(to)) because lemmaRangeEndEqual(a, b, from, to) } && + check { (from <= to - 1) ==> isRangeEqual(a, b, from, to - 1) } // FIXME timeout + ) + }.holds // FIXME TIMEOUT + + private def lemmaMiniSubRangeEqual1(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + isRangeEqual(a, b, from, from) because a(from) == b(from) + ) + }.holds + + private def lemmaMiniSubRangeEqual2(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + isRangeEqual(a, b, from, to) ==> ( + isRangeEqual(a, b, to, to) because { + if (from == to) check { a(to) == b(to) } + else { + check { from < to } && + check { lemmaMiniSubRangeEqual2(a, b, from + 1, to) } && + check { lemmaMiniSubRangeEqual2(a, b, from, to - 1) } + } + } + ) + }.holds + + private def lemmaUnitRangeEqual(a: Array[Byte], b: Array[Byte], pos: Int): Boolean = { + require(0 <= pos && pos < a.length && pos < b.length) + isRangeEqual(a, b, pos, pos) ==> (a(pos) == b(pos)) + }.holds + + private def lemmaRangeEndEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length && isRangeEqual(a, b, from, to)) + ( a(to) == b(to) ) because { + if (from == to) trivial + else { + check { from < to } && + check { lemmaRangeEndEqual(a, b, from + 1, to) } + } + } + }.holds + + + private def lemmaCodeWordIndexEquality(index: Int): Boolean = { + require(0 <= index && index < 65536) + index == codeWord2Index(index2CodeWord(index)) + }.holds + + + private def lemmaAllValidBuffers(buffers: Array[Buffer]): Boolean = { + allValidBuffers(buffers) + }.holds // FIXME TIMEOUT + + + + private def testIsRangeEqual(): Boolean = { + val a = Array[Byte](1, 0, 0, 0, 0, 0, 0, 0, 0, 0) + val b = Array[Byte](-128, 0, 0, 0, 0, 0, 0, 0, 0, 0) + !isRangeEqual(a, b, 0, 0) + }.holds + + private def testBufferIsEqual(): Boolean = { + val a = createBuffer() + val b = createBuffer() + + a.append(1) + b.append(-128) + + assert(a.size == b.size) + assert(a.nonEmpty) + assert(b.nonEmpty) + assert(a.isEmpty == b.isEmpty) + + !a.isEqual(b) + }.holds + + + // Helper for range equality checking + private def isRangeEqual(a: Array[Byte], b: Array[Byte], from: Int, to: Int): Boolean = { + require(0 <= from && from <= to && to < a.length && to < b.length) + a(from) == b(from) && { + if (from == to) true + else isRangeEqual(a, b, from + 1, to) + } + } + + + private def allValidBuffers(buffers: Array[Buffer]): Boolean = { + def rec(from: Int): Boolean = { + require(0 <= from && from <= buffers.length) + if (from < buffers.length) buffers(from).isValid && rec(from + 1) + else true + } + + rec(0) + } + + + // A buffer representation using a fix-sized array for memory. + // + // NOTE Use `createBuffer()` to get a new buffer; don't attempt to create one yourself. + case class Buffer(private val array: Array[Byte], private var length: Int) { + val capacity = array.length + require(isValid) + + def isValid: Boolean = length >= 0 && length <= capacity && capacity == BufferSize + + def isFull: Boolean = length == capacity + + def nonFull: Boolean = length < capacity + + def isEmpty: Boolean = length == 0 + + def nonEmpty: Boolean = length > 0 + + def isEqual(b: Buffer): Boolean = { + if (b.length != length) false + else { isEmpty || isRangeEqual(array, b.array, 0, length - 1) } + } //ensuring { _ => this.isEqual(old(this)) && b.isEqual(old(b)) } // this VC does infinite recursion + + def size = { + length + } ensuring { res => 0 <= res && res <= capacity } + + def apply(index: Int): Byte = { + require(index >= 0 && index < length) + array(index) + } + + def append(x: Byte): Unit = { + require(nonFull) + + array(length) = x + + length += 1 + } ensuring { _ => isValid } + + def dropLast(): Unit = { + require(nonEmpty) + + length -= 1 + } ensuring { _ => isValid } //&& old(this).length == length + 1 } + + def clear(): Unit = { + length = 0 + } ensuring { _ => isEmpty && isValid } + + def set(b: Buffer): Unit = { + if (b.isEmpty) clear + else setImpl(b) + } ensuring { _ => b.isValid && isValid && isEqual(b) /* && b.isEqual(old(b)) */ } + + private def setImpl(b: Buffer): Unit = { + require(b.nonEmpty) + + length = b.length + + assert(b.isValid) + assert(isValid) + assert(nonEmpty) + assert(length == b.length) + + var i = 0 + (while (i < length) { + assert(isValid) + assert(nonEmpty) + assert(length == b.length) + assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) + + array(i) = b.array(i) + i += 1 + + assert(isValid) + assert(nonEmpty) + assert(length == b.length) + assert(i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) + }) invariant { // FIXME TIMEOUT + 0 <= i && i <= length && + // lengthCheckpoint == b.length && lengthCheckpoint == length && // no mutation of the length + isValid && nonEmpty && + length == b.length && + (i > 0 ==> isRangeEqual(array, b.array, 0, i - 1)) // avoid OutOfBoundAccess + } + + assert(b.isValid) + assert(isValid) + assert(nonEmpty) + assert(isRangeEqual(array, b.array, 0, length - 1)) + assert(length == b.length) + assert(isEqual(b)) + } ensuring { _ => b.isValid && isValid && nonEmpty && isEqual(b) /* && b.isEqual(old(b)) */ } + + } + + @inline // very important because we cannot return arrays + def createBuffer(): Buffer = { + Buffer(Array.fill(BufferSize)(0), 0) + } ensuring { b => b.isEmpty && b.nonFull && b.isValid } + + + def tryReadNext(fis: FIS)(implicit state: leon.io.State): Option[Byte] = { + require(fis.isOpen) + fis.tryReadByte() + } + + def writeCodeWord(fos: FOS, cw: CodeWord): Boolean = { + require(fos.isOpen) + fos.write(cw.b1) && fos.write(cw.b2) + } + + def tryReadCodeWord(fis: FIS)(implicit state: leon.io.State): Option[CodeWord] = { + require(fis.isOpen) + val b1Opt = fis.tryReadByte() + val b2Opt = fis.tryReadByte() + + (b1Opt, b2Opt) match { + case (Some(b1), Some(b2)) => Some(CodeWord(b1, b2)) + case _ => None() + } + } + + def writeBytes(fos: FOS, buffer: Buffer): Boolean = { + require(fos.isOpen && buffer.nonEmpty) + var success = true + var i = 0 + + val size = buffer.size + + (while (success && i < size) { + success = fos.write(buffer(i)) + i += 1 + }) invariant { + 0 <= i && i <= size + } + + success + } + + case class CodeWord(b1: Byte, b2: Byte) // a 16-bit code word + + def index2CodeWord(index: Int): CodeWord = { + require(0 <= index && index < 65536) // unsigned index + // Shift the index in the range [-32768, 32767] to make it signed + val signed = index - 32768 + // Split it into two byte components + val b2 = signed.toByte + val b1 = (signed >>> 8).toByte + CodeWord(b1, b2) + } + + def codeWord2Index(cw: CodeWord): Int = { + // When building the signed integer back, make sure to understand integer + // promotion with negative numbers: we need to avoid the signe extension here. + val signed = (cw.b1 << 8) | (0xff & cw.b2) + signed + 32768 + } ensuring { res => 0 <= res && res < 65536 } + + + case class Dictionary(private val buffers: Array[Buffer], private var nextIndex: Int) { + val capacity = buffers.length + require(isValid) + + def isValid = 0 <= nextIndex && nextIndex <= capacity && capacity == DictionarySize && allValidBuffers(buffers) + + def isEmpty = nextIndex == 0 + + def nonEmpty = !isEmpty + + def isFull = nextIndex == capacity + + def nonFull = nextIndex < capacity + + def lastIndex = { + require(nonEmpty) + nextIndex - 1 + } ensuring { res => 0 <= res && res < capacity } + + def contains(index: Int): Boolean = { + require(0 <= index) + index < nextIndex + } + + def appendTo(index: Int, buffer: Buffer): Boolean = { + require(0 <= index && contains(index)) + + val size = buffers(index).size + + assert(buffer.capacity == BufferSize) + if (buffer.size < buffer.capacity - size) { + assert(buffer.nonFull) + + var i = 0 + (while (i < size) { + buffer.append(buffers(index)(i)) + i += 1 + }) invariant { + 0 <= i && i <= size && + (i < size ==> buffer.nonFull) + } + + true + } else false + } + + def insert(b: Buffer): Unit = { + require(nonFull && b.nonEmpty) + + assert(lemmaSize) + assert(isValid) + assert(nonFull) + assert(nextIndex < capacity) + assert(nextIndex < DictionarySize) + assert(nextIndex + 1 <= DictionarySize) + + buffers(nextIndex).set(b) // FIXME TIMEOUT (?) + + assert(lemmaSize) + assert(isValid) // FIXME TIMEOUT + assert(nonFull) // FIXME TIMEOUT + assert(nextIndex < capacity) // FIXME TIMEOUT + assert(nextIndex < DictionarySize) // FIXME TIMEOUT + assert(nextIndex + 1 <= DictionarySize) // FIXME TIMEOUT + + nextIndex += 1 // FIXME TIMEOUT + } ensuring { _ => isValid } // FIXME TIMEOUT + + def encode(b: Buffer): Option[CodeWord] = { + require(b.nonEmpty) + + var found = false + var i = 0 + + (while (!found && i < nextIndex) { + if (buffers(i).isEqual(b)) { + found = true + } else { + i += 1 + } + }) invariant { + 0 <= i && i <= nextIndex && i <= capacity && + isValid && + (found ==> (i < nextIndex && buffers(i).isEqual(b))) + } + + if (found) Some(index2CodeWord(i)) else None() + } + } + + @inline // in order to "return" the arrays + def createDictionary() = { + Dictionary(Array.fill(DictionarySize){ createBuffer() }, 0) + } ensuring { res => res.isEmpty } + + def initialise(dict: Dictionary): Unit = { + require(dict.isEmpty) // initialise only fresh dictionaries + + val buffer = createBuffer() + assert(buffer.isEmpty) + + var value: Int = Byte.MinValue // Use an Int to avoid overflow issues + + (while (value <= Byte.MaxValue) { + buffer.append(value.toByte) // no truncation here + dict.insert(buffer) + buffer.dropLast() + value += 1 + }) invariant { // FIXME TIMEOUT + dict.nonFull && + buffer.isEmpty && + value >= Byte.MinValue && value <= Byte.MaxValue + 1 // last iteration goes "overflow" on Byte + } + } ensuring { _ => dict.isValid && dict.nonEmpty } + + def encode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) + + // Initialise the dictionary with the basic alphabet + val dictionary = createDictionary() + initialise(dictionary) + + // Small trick to move the static arrays outside the main encoding function; + // this helps analysing the C code in a debugger (less local variables) but + // it actually has no impact on performance (or should, in theory). + encodeImpl(dictionary, fis, fos) + } + + def encodeImpl(dictionary: Dictionary, fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen && dictionary.nonEmpty) + + var bufferFull = false // TODO handle this as a non-fatal thing. + var ioError = false + + val buffer = createBuffer() + assert(buffer.isEmpty && buffer.nonFull) + + var currentOpt = tryReadNext(fis) + + // Read from the input file all its content, stop when an error occurs + // (either output error or full buffer) + (while (!bufferFull && !ioError && currentOpt.isDefined) { + val c = currentOpt.get + + assert(buffer.nonFull) + buffer.append(c) + assert(buffer.nonEmpty) + + val code = dictionary.encode(buffer) + + val processBuffer = buffer.isFull || code.isEmpty + + if (processBuffer) { + // Add s (with c) into the dictionary, if the dictionary size limitation allows it + if (dictionary.nonFull) { + dictionary.insert(buffer) + } + + // Encode s (without c) and print it + buffer.dropLast() + assert(buffer.nonFull) + assert(buffer.nonEmpty) + val code2 = dictionary.encode(buffer) + + assert(code2.isDefined) // (*) + // To prove (*) we might need to: + // - prove the dictionary can encode any 1-length buffer + // - the buffer was empty when entering the loop or + // that the initial buffer was in the dictionary. + ioError = !writeCodeWord(fos, code2.get) + + // Prepare for next codeword: set s to c + buffer.clear() + buffer.append(c) + assert(buffer.nonEmpty) + } + + bufferFull = buffer.isFull + + currentOpt = tryReadNext(fis) + }) invariant { + bufferFull == buffer.isFull && + ((!bufferFull && !ioError) ==> buffer.nonEmpty) // it might always be true... + } + + // Process the remaining buffer + if (!bufferFull && !ioError) { + val code = dictionary.encode(buffer) + assert(code.isDefined) // See (*) above. + ioError = !writeCodeWord(fos, code.get) + } + + !bufferFull && !ioError + } + + def decode(fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen) + + // Initialise the dictionary with the basic alphabet + val dictionary = createDictionary() + initialise(dictionary) + + decodeImpl(dictionary, fis, fos) + } + + def decodeImpl(dictionary: Dictionary, fis: FIS, fos: FOS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && fos.isOpen && dictionary.nonEmpty) + + var illegalInput = false + var ioError = false + var bufferFull = false + + var currentOpt = tryReadCodeWord(fis) + + val buffer = createBuffer() + + if (currentOpt.isDefined) { + val cw = currentOpt.get + val index = codeWord2Index(cw) + + if (dictionary contains index) { + bufferFull = !dictionary.appendTo(index, buffer) + ioError = !writeBytes(fos, buffer) + } else { + illegalInput = true + } + + currentOpt = tryReadCodeWord(fis) + } + + (while (!illegalInput && !ioError && !bufferFull && currentOpt.isDefined) { + val cw = currentOpt.get + val index = codeWord2Index(cw) + val entry = createBuffer() + + if (dictionary contains index) { + illegalInput = !dictionary.appendTo(index, entry) + } else if (index == dictionary.lastIndex + 1) { + entry.set(buffer) + entry.append(buffer(0)) + } else { + illegalInput = true + } + + ioError = !writeBytes(fos, entry) + bufferFull = buffer.isFull + + if (!bufferFull) { + val tmp = createBuffer() + tmp.set(buffer) + tmp.append(entry(0)) + if (dictionary.nonFull) { + dictionary.insert(tmp) + } + + buffer.set(entry) + } + + currentOpt = tryReadCodeWord(fis) + }) invariant { + dictionary.nonEmpty + } + + !illegalInput && !ioError && !bufferFull + } + sealed abstract class Status + case class Success() extends Status + case class OpenError() extends Status + case class EncodeError() extends Status + case class DecodeError() extends Status + + implicit def status2boolean(s: Status): Boolean = s match { + case Success() => true + case _ => false + } + + def _main() = { + implicit val state = leon.io.newState + + def statusCode(s: Status): Int = s match { + case Success() => StdOut.println("success"); 0 + case OpenError() => StdOut.println("couldn't open file"); 1 + case EncodeError() => StdOut.println("encoding failed"); 2 + case DecodeError() => StdOut.println("decoding failed"); 3 + } + + def encodeFile(): Status = { + val input = FIS.open("input.txt") + val encoded = FOS.open("encoded.txt") + + val res = + if (input.isOpen && encoded.isOpen) { + if (encode(input, encoded)) Success() + else EncodeError() + } else OpenError() + + encoded.close + input.close + + res + } + + def decodeFile(): Status = { + val encoded = FIS.open("encoded.txt") + val decoded = FOS.open("decoded.txt") + + val res = + if (encoded.isOpen && decoded.isOpen) { + if (decode(encoded, decoded)) Success() + else DecodeError() + } else OpenError() + + decoded.close + encoded.close + + res + } + + val r1 = encodeFile() + statusCode(if (r1) decodeFile() else r1) + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + diff --git a/src/test/resources/regression/genc/unverified/LZW.scala b/src/test/resources/regression/genc/unverified/LZWb.scala similarity index 100% rename from src/test/resources/regression/genc/unverified/LZW.scala rename to src/test/resources/regression/genc/unverified/LZWb.scala From c8ecf7cb50cc9405f4f52fec5449f0f38152cac9 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 14 Dec 2016 16:19:29 +0100 Subject: [PATCH 64/77] Move some tests around - Aliasing3 is disabled because it make verification crash (see #289) - Aliasing4 is disabled because it make verification produce incorrect counter-example (see #287) - Inheritance9 is disabled because it make xlang crash (see #288) - UsingConcreteCLasses is enabled because verification is easy --- .../regression/genc/{valid => unverified}/Aliasing3.scala | 0 .../regression/genc/{valid => unverified}/Aliasing4.scala | 0 .../regression/genc/{valid => unverified}/Inheritance9.scala | 0 .../regression/genc/{valid => unverified}/LinearSearch.scala | 0 .../genc/{unverified => valid}/UsingConcreteClasses.scala | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename src/test/resources/regression/genc/{valid => unverified}/Aliasing3.scala (100%) rename src/test/resources/regression/genc/{valid => unverified}/Aliasing4.scala (100%) rename src/test/resources/regression/genc/{valid => unverified}/Inheritance9.scala (100%) rename src/test/resources/regression/genc/{valid => unverified}/LinearSearch.scala (100%) rename src/test/resources/regression/genc/{unverified => valid}/UsingConcreteClasses.scala (93%) diff --git a/src/test/resources/regression/genc/valid/Aliasing3.scala b/src/test/resources/regression/genc/unverified/Aliasing3.scala similarity index 100% rename from src/test/resources/regression/genc/valid/Aliasing3.scala rename to src/test/resources/regression/genc/unverified/Aliasing3.scala diff --git a/src/test/resources/regression/genc/valid/Aliasing4.scala b/src/test/resources/regression/genc/unverified/Aliasing4.scala similarity index 100% rename from src/test/resources/regression/genc/valid/Aliasing4.scala rename to src/test/resources/regression/genc/unverified/Aliasing4.scala diff --git a/src/test/resources/regression/genc/valid/Inheritance9.scala b/src/test/resources/regression/genc/unverified/Inheritance9.scala similarity index 100% rename from src/test/resources/regression/genc/valid/Inheritance9.scala rename to src/test/resources/regression/genc/unverified/Inheritance9.scala diff --git a/src/test/resources/regression/genc/valid/LinearSearch.scala b/src/test/resources/regression/genc/unverified/LinearSearch.scala similarity index 100% rename from src/test/resources/regression/genc/valid/LinearSearch.scala rename to src/test/resources/regression/genc/unverified/LinearSearch.scala diff --git a/src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala b/src/test/resources/regression/genc/valid/UsingConcreteClasses.scala similarity index 93% rename from src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala rename to src/test/resources/regression/genc/valid/UsingConcreteClasses.scala index 8725a6e20..434c01d3f 100644 --- a/src/test/resources/regression/genc/unverified/UsingConcreteClasses.scala +++ b/src/test/resources/regression/genc/valid/UsingConcreteClasses.scala @@ -13,7 +13,7 @@ object UsingConcreteClasses { if (child.opt.v == 42) 0 else 1 - } + } ensuring { _ == 0 } @extern def main(args: Array[String]): Unit = _main() From 8ca576f7153aa5471b84ba6c1c45a99e836a7a51 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 14 Dec 2016 16:25:10 +0100 Subject: [PATCH 65/77] Fix LZW object names for testing purposes --- src/test/resources/regression/genc/unverified/LZWa.scala | 2 +- src/test/resources/regression/genc/unverified/LZWb.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/regression/genc/unverified/LZWa.scala b/src/test/resources/regression/genc/unverified/LZWa.scala index d38db2039..2ffadbfc5 100644 --- a/src/test/resources/regression/genc/unverified/LZWa.scala +++ b/src/test/resources/regression/genc/unverified/LZWa.scala @@ -10,7 +10,7 @@ import leon.io.{ StdOut } -object LZW { +object LZWa { // GENERAL NOTES // ============= diff --git a/src/test/resources/regression/genc/unverified/LZWb.scala b/src/test/resources/regression/genc/unverified/LZWb.scala index 775787c6c..22c36ac46 100644 --- a/src/test/resources/regression/genc/unverified/LZWb.scala +++ b/src/test/resources/regression/genc/unverified/LZWb.scala @@ -10,7 +10,7 @@ import leon.io.{ StdOut } -object LZW { +object LZWb { // GENERAL NOTES // ============= From ec37922a06cfaa710cad88c4404871d9e4f65609 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 14 Dec 2016 16:43:09 +0100 Subject: [PATCH 66/77] Moving LZW implementations to a new testcases section Because they are slow to generate, slow to compile and slow to execute, they cannot be part of the regression suite. --- .../regression/genc/unverified => testcases/genc}/LZWa.scala | 0 .../regression/genc/unverified => testcases/genc}/LZWb.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src/test/resources/regression/genc/unverified => testcases/genc}/LZWa.scala (100%) rename {src/test/resources/regression/genc/unverified => testcases/genc}/LZWb.scala (100%) diff --git a/src/test/resources/regression/genc/unverified/LZWa.scala b/testcases/genc/LZWa.scala similarity index 100% rename from src/test/resources/regression/genc/unverified/LZWa.scala rename to testcases/genc/LZWa.scala diff --git a/src/test/resources/regression/genc/unverified/LZWb.scala b/testcases/genc/LZWb.scala similarity index 100% rename from src/test/resources/regression/genc/unverified/LZWb.scala rename to testcases/genc/LZWb.scala From 17481484aa28cf0843d4b306634935f2f6d43a5f Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 13 Jan 2017 15:11:42 +0100 Subject: [PATCH 67/77] Fix generation of union --- src/main/scala/leon/genc/CPrinter.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/scala/leon/genc/CPrinter.scala b/src/main/scala/leon/genc/CPrinter.scala index c6fb27e72..c1061903a 100644 --- a/src/main/scala/leon/genc/CPrinter.scala +++ b/src/main/scala/leon/genc/CPrinter.scala @@ -191,7 +191,11 @@ class CPrinter(val sb: StringBuffer = new StringBuffer) { |} $id;""" case TypeDef(t: DataType) => - c"""|typedef struct { + val kind = t match { + case _: Struct => "struct" + case _: Union => "union" + } + c"""|typedef $kind { | ${nary(t.fields, sep = ";\n", closing = ";")} |} ${t.id};""" From 46be524c5b9a0bfae82f8fe8429ecee55bd6380b Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 13 Jan 2017 15:12:06 +0100 Subject: [PATCH 68/77] Update LZW parameters --- testcases/genc/LZWa.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/genc/LZWa.scala b/testcases/genc/LZWa.scala index 2ffadbfc5..b27286ead 100644 --- a/testcases/genc/LZWa.scala +++ b/testcases/genc/LZWa.scala @@ -22,7 +22,7 @@ object LZWa { // We limit the size of the dictionary to an arbitrary size, less than or equals to 2^16. @inline - val DictionarySize = 4096 + val DictionarySize = 8192 // We use fix-sized buffers @inline From 70138c53c1ab26dcb61672164776be5861ccaeea Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Wed, 18 Jan 2017 20:42:09 +0100 Subject: [PATCH 69/77] Use block comment in C code --- library/leon/io/FileInputStream.scala | 2 +- library/leon/io/FileOutputStream.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/leon/io/FileInputStream.scala b/library/leon/io/FileInputStream.scala index 748681b78..87abc8c11 100644 --- a/library/leon/io/FileInputStream.scala +++ b/library/leon/io/FileInputStream.scala @@ -23,7 +23,7 @@ object FileInputStream { """ |static FILE* __FUNCTION__(char* filename, void* unused) { | FILE* this = fopen(filename, "r"); - | // this == NULL on failure + | /* this == NULL on failure */ | return this; |} """ diff --git a/library/leon/io/FileOutputStream.scala b/library/leon/io/FileOutputStream.scala index 120e00c06..acf39902f 100644 --- a/library/leon/io/FileOutputStream.scala +++ b/library/leon/io/FileOutputStream.scala @@ -26,7 +26,7 @@ object FileOutputStream { """ |static FILE* __FUNCTION__(char* filename) { | FILE* this = fopen(filename, "w"); - | // this == NULL on failure + | /* this == NULL on failure */ | return this; |} """ From f77238b6f7f587e70e74da72160f65fe9f0f7342 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 09:38:39 +0100 Subject: [PATCH 70/77] Add support for Tuple6 to Tuple8 --- .../leon/frontends/scalac/ASTExtractors.scala | 31 +++++++++++++++++++ .../frontends/scalac/CodeExtraction.scala | 11 ++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/scala/leon/frontends/scalac/ASTExtractors.scala b/src/main/scala/leon/frontends/scalac/ASTExtractors.scala index fb064a0d5..e2ec8942d 100644 --- a/src/main/scala/leon/frontends/scalac/ASTExtractors.scala +++ b/src/main/scala/leon/frontends/scalac/ASTExtractors.scala @@ -42,6 +42,9 @@ trait ASTExtractors { protected lazy val tuple3Sym = classFromName("scala.Tuple3") protected lazy val tuple4Sym = classFromName("scala.Tuple4") protected lazy val tuple5Sym = classFromName("scala.Tuple5") + protected lazy val tuple6Sym = classFromName("scala.Tuple6") + protected lazy val tuple7Sym = classFromName("scala.Tuple7") + protected lazy val tuple8Sym = classFromName("scala.Tuple8") protected lazy val scalaMapSym = classFromName("scala.collection.immutable.Map") protected lazy val scalaSetSym = classFromName("scala.collection.immutable.Set") protected lazy val setSym = classFromName("leon.lang.Set") @@ -63,6 +66,9 @@ trait ASTExtractors { def isTuple3(sym : Symbol) : Boolean = sym == tuple3Sym def isTuple4(sym : Symbol) : Boolean = sym == tuple4Sym def isTuple5(sym : Symbol) : Boolean = sym == tuple5Sym + def isTuple6(sym : Symbol) : Boolean = sym == tuple6Sym + def isTuple7(sym : Symbol) : Boolean = sym == tuple7Sym + def isTuple8(sym : Symbol) : Boolean = sym == tuple8Sym def isBigIntSym(sym : Symbol) : Boolean = getResolvedTypeSym(sym) == bigIntSym @@ -971,6 +977,31 @@ trait ASTExtractors { case TypeRef(_, sym, List(t1, t2, t3, t4, t5)) => Some((Seq(t1, t2, t3, t4, t5), Seq(e1, e2, e3, e4, e5))) case _ => None } + + case Apply( + Select(New(tupleType), _), + List(e1, e2, e3, e4, e5, e6) + ) if tupleType.symbol == tuple6Sym => tupleType.tpe match { + case TypeRef(_, sym, List(t1, t2, t3, t4, t5, t6)) => Some((Seq(t1, t2, t3, t4, t5, t6), Seq(e1, e2, e3, e4, e5, e6))) + case _ => None + } + + case Apply( + Select(New(tupleType), _), + List(e1, e2, e3, e4, e5, e6, e7) + ) if tupleType.symbol == tuple7Sym => tupleType.tpe match { + case TypeRef(_, sym, List(t1, t2, t3, t4, t5, t6, t7)) => Some((Seq(t1, t2, t3, t4, t5, t6), Seq(e1, e2, e3, e4, e5, e6, e7))) + case _ => None + } + + case Apply( + Select(New(tupleType), _), + List(e1, e2, e3, e4, e5, e6, e7, e8) + ) if tupleType.symbol == tuple8Sym => tupleType.tpe match { + case TypeRef(_, sym, List(t1, t2, t3, t4, t5, t6, t7, t8)) => Some((Seq(t1, t2, t3, t4, t5, t6), Seq(e1, e2, e3, e4, e5, e6, e7, e8))) + case _ => None + } + // Match e1 -> e2 case Apply(TypeApply(Select(Apply(TypeApply(ExSelected("scala", "Predef", "ArrowAssoc"), List(tpeFrom)), List(from)), ExNamed("$minus$greater")), List(tpeTo)), List(to)) => diff --git a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala index fa1582e05..da59ac798 100644 --- a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala +++ b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala @@ -504,7 +504,7 @@ trait CodeExtraction extends ASTExtractors { }) }) } - + /** Returns the function associated to the symbol. * In the case of varargs, if the function is not found * and there are others with the same name in the same scope, @@ -2373,6 +2373,15 @@ trait CodeExtraction extends ASTExtractors { case TypeRef(_, sym, List(t1,t2,t3,t4,t5)) if isTuple5(sym) => TupleType(Seq(extractType(t1),extractType(t2),extractType(t3),extractType(t4),extractType(t5))) + case TypeRef(_, sym, List(t1,t2,t3,t4,t5,t6)) if isTuple6(sym) => + TupleType(Seq(extractType(t1),extractType(t2),extractType(t3),extractType(t4),extractType(t5),extractType(t6))) + + case TypeRef(_, sym, List(t1,t2,t3,t4,t5,t6,t7)) if isTuple7(sym) => + TupleType(Seq(extractType(t1),extractType(t2),extractType(t3),extractType(t4),extractType(t5),extractType(t6),extractType(t7))) + + case TypeRef(_, sym, List(t1,t2,t3,t4,t5,t6,t7,t8)) if isTuple8(sym) => + TupleType(Seq(extractType(t1),extractType(t2),extractType(t3),extractType(t4),extractType(t5),extractType(t6),extractType(t7),extractType(t8))) + case TypeRef(_, sym, btt :: Nil) if isArrayClassSym(sym) => ArrayType(extractType(btt)) From a583e1b8717ac73a54174061b83566440ae30c2a Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 16:22:10 +0100 Subject: [PATCH 71/77] Remove RemoveVCPhase, for performance and issue reasons --- src/main/scala/leon/genc/GenerateCPhase.scala | 3 +- .../leon/genc/phases/Scala2IRPhase.scala | 5 ++ src/main/scala/leon/utils/RemoveVCPhase.scala | 55 ------------------- 3 files changed, 6 insertions(+), 57 deletions(-) delete mode 100644 src/main/scala/leon/utils/RemoveVCPhase.scala diff --git a/src/main/scala/leon/genc/GenerateCPhase.scala b/src/main/scala/leon/genc/GenerateCPhase.scala index e5cf144ac..12ddca679 100644 --- a/src/main/scala/leon/genc/GenerateCPhase.scala +++ b/src/main/scala/leon/genc/GenerateCPhase.scala @@ -4,7 +4,7 @@ package leon package genc import purescala.Definitions.{ Program } -import utils.{ DebugSectionTrees, RemoveVCPhase } +import utils.{ DebugSectionTrees } import phases._ @@ -14,7 +14,6 @@ object GenerateCPhase extends LeonPhase[Program, CAST.Prog] { val description = "Preprocess and convert Leon's AST to C" val pipeline = - RemoveVCPhase andThen Pipeline.both(NoopPhase[Program], ExtractEntryPointPhase) andThen ComputeDependenciesPhase andThen Pipeline.both(NoopPhase[Dependencies], ComputeFunCtxPhase) andThen diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 882ea38ab..590381b26 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -504,6 +504,11 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe } private def rec(e: Expr)(implicit env: Env, tm0: TypeMapping): CIR.Expr = e match { + /* Ignore static assertions */ + case Require(_, body) => rec(body) + case Ensuring(body, _) => rec(body) + case Assert(_, _, body) => rec(body) + case UnitLiteral() => CIR.Lit(L.UnitLit) case BooleanLiteral(v) => CIR.Lit(L.BoolLit(v)) case ByteLiteral(v) => CIR.Lit(L.Int8Lit(v)) diff --git a/src/main/scala/leon/utils/RemoveVCPhase.scala b/src/main/scala/leon/utils/RemoveVCPhase.scala deleted file mode 100644 index f6ba256b8..000000000 --- a/src/main/scala/leon/utils/RemoveVCPhase.scala +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright 2009-2016 EPFL, Lausanne */ - -package leon.utils - -import leon.{ LeonContext, TransformationPhase } -import leon.purescala.Definitions.{ ClassDef, FunDef, Program } -import leon.purescala.DefOps.{ definitionReplacer, transformProgram } -import leon.purescala.Expressions.{ Assert, Ensuring, Expr, Require } -import leon.purescala.ExprOps.{ preMap } - -/* - * This phase removes all verification condition it can find in the given - * program, without mutating it! - * - * NOTE This phase expects methods to have been lifted first. - */ -object RemoveVCPhase extends TransformationPhase { - - val name = "Remove verification conditions" - val description = "Remove assertions, loop invariant pre & postconditions" - - def apply(ctx: LeonContext, p: Program): Program = { - ctx.reporter.debug("Running VC removal phase")(leon.utils.DebugSectionLeon) - val timer = ctx.timers.removeVC.start() - - val res = transformProgram(transformer, p) - - timer.stop() - res - } - - // This transformer will replace function definitions and their invocations with a new - // version without VC. - private lazy val transformer = definitionReplacer(funMapper, noClassMapper) - - private def funMapper(fd: FunDef): Option[FunDef] = Some(funMapperImpl(fd)) - - private def funMapperImpl(fd: FunDef): FunDef = { - val newFd = fd.duplicate() - newFd.fullBody = preMap(exprMapper, applyRec = true)(newFd.fullBody) - newFd - } - - // Actually removed VC expressions - private def exprMapper(expr: Expr): Option[Expr] = expr match { - case Require(_, body) => Some(body) - case Ensuring(body, _) => Some(body) - case Assert(_, _, body) => Some(body) - case _ => None - } - - // Because we assume methods were lifted from classes, we don't need to procees them. - private def noClassMapper(cd: ClassDef): Option[ClassDef] = None -} - From 05c213636c57a7f09d54b8dcfaeb4b366804d108 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 16:23:05 +0100 Subject: [PATCH 72/77] Minor improvement of scrutinee handling in pattern matching --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 590381b26..3248b27ac 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -270,6 +270,7 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe case v: Variable => (v, None, env) case Block(Nil, v: Variable) => (v, None, env) case Block(init, v: Variable) => (v, Some(rec(Block(init.init, init.last))), env) + case field @ CaseClassSelector(_, _: Variable, _) => (field, None, env) case _: FunctionInvocation | _: CaseClass | _: LetVar | _: Let | _: Tuple => withTmp(scrutinee0.getType, scrutinee0, env) From a35be9ad4c94c7cbe5d2385da93a0164da51fe3f Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Tue, 31 Jan 2017 17:39:40 +0100 Subject: [PATCH 73/77] Report special assertion as Overflows --- src/main/scala/leon/verification/DefaultTactic.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/leon/verification/DefaultTactic.scala b/src/main/scala/leon/verification/DefaultTactic.scala index e074ff974..ccd57bab8 100644 --- a/src/main/scala/leon/verification/DefaultTactic.scala +++ b/src/main/scala/leon/verification/DefaultTactic.scala @@ -88,7 +88,7 @@ class DefaultTactic(vctx: VerificationContext) extends Tactic(vctx) { case BVRemainder(_, _) | BVShiftLeft(_, _) | BVAShiftRight(_, _) | BVLShiftRight(_, _) => VCKinds.StrictArithmetic - case BVPlus(_, _) | BVMinus(_, _) | BVUMinus(_) | BVTimes(_, _) => + case BVPlus(_, _) | BVMinus(_, _) | BVUMinus(_) | BVDivision(_, _) | BVTimes(_, _) => VCKinds.ArithmeticOverflow case _ => From 3b534206373a145a04d3cc195f57c6b1873c9127 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 16:31:32 +0100 Subject: [PATCH 74/77] Add regression test for GenC --- .../genc/valid/RegressionTest1.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/resources/regression/genc/valid/RegressionTest1.scala diff --git a/src/test/resources/regression/genc/valid/RegressionTest1.scala b/src/test/resources/regression/genc/valid/RegressionTest1.scala new file mode 100644 index 000000000..6900d32b3 --- /dev/null +++ b/src/test/resources/regression/genc/valid/RegressionTest1.scala @@ -0,0 +1,57 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +import leon.io.{ + FileInputStream => FIS +} + +/* + * This test was reduced from a bigger one. A bug caused GenC to crash + * when converting `processImage` from Scala to its IR because there was + * a mismatch between the function argument name and the one used in its + * body: bh$2 vs bh$1. This mismatch in identifier was generated by + * RemoveVCPhase, for some obscure reasons. + */ +object RegressionTest1 { + + case class BitmapHeader(width: Int, height: Int) { + require(0 <= width && 0 <= height) + } + + def maybeReadBitmapHeader(fis: FIS)(implicit state: leon.io.State): BitmapHeader = { + require(fis.isOpen) + BitmapHeader(16, 16) + } + + def process(fis: FIS)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen) + + def processImage(bh: BitmapHeader): Boolean = { + if (bh.width > 0) true + else false + } + + val bitmapHeader = maybeReadBitmapHeader(fis) + processImage(bitmapHeader) + } + + def _main(): Int = { + implicit val state = leon.io.newState + val input = FIS.open("input.bmp") + + if (input.isOpen) { + process(input) + () + } + + 0 + } + + @extern + def main(args: Array[String]): Unit = _main() + +} + + From 5e84253858b13addf07813d5891af0c5f3710759 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 19:26:52 +0100 Subject: [PATCH 75/77] Add support for more scrutinee kind --- src/main/scala/leon/genc/phases/Scala2IRPhase.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala index 3248b27ac..735504e8a 100644 --- a/src/main/scala/leon/genc/phases/Scala2IRPhase.scala +++ b/src/main/scala/leon/genc/phases/Scala2IRPhase.scala @@ -266,18 +266,22 @@ private class S2IRImpl(val ctx: LeonContext, val ctxDB: FunCtxDB, val deps: Depe (tmp0.toVariable, Some(pre), env + ((tmp0, instantiate(typ, tm)) -> tmp)) } - val (scrutinee, preOpt, newEnv) = scrutinee0 match { + def scrutRec(scrutinee0: Expr): (Expr, Option[CIR.Expr], Env) = scrutinee0 match { case v: Variable => (v, None, env) case Block(Nil, v: Variable) => (v, None, env) case Block(init, v: Variable) => (v, Some(rec(Block(init.init, init.last))), env) case field @ CaseClassSelector(_, _: Variable, _) => (field, None, env) - case _: FunctionInvocation | _: CaseClass | _: LetVar | _: Let | _: Tuple => + case Assert(_, _, body) => scrutRec(body) + + case _: FunctionInvocation | _: CaseClass | _: LetVar | _: Let | _: Tuple | _: IfExpr => withTmp(scrutinee0.getType, scrutinee0, env) case e => internalError(s"scrutinee = $e of type ${e.getClass} is not supported") } + val (scrutinee, preOpt, newEnv) = scrutRec(scrutinee0) + val cases = cases0 map { caze => convertCase(scrutinee, caze)(newEnv, tm) } // Identify the last case From bae8c9aaad29eb554310474568a9ed3226021ab1 Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Mon, 30 Jan 2017 18:35:48 +0100 Subject: [PATCH 76/77] Fix typos --- .../scala/leon/genc/ir/Referentiator.scala | 2 +- .../genc/unverified/IntegralColor.scala | 49 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/main/scala/leon/genc/ir/Referentiator.scala b/src/main/scala/leon/genc/ir/Referentiator.scala index 34e13ad25..80b3f813c 100644 --- a/src/main/scala/leon/genc/ir/Referentiator.scala +++ b/src/main/scala/leon/genc/ir/Referentiator.scala @@ -281,7 +281,7 @@ final class Referentiator(val ctx: LeonContext) extends Transformer(LIR, RIR) wi val isRef = typeInfos.head if (typeInfos exists { _ != isRef }) { fatalError(s"Cannot apply reference because of normalisation inconsistency on types. " + - s"Inciminated variable: ${vd.id}; Use `--debug=trees` option to learn why it failed.") + s"Incriminated variable: ${vd.id}; Use `--debug=trees` option to learn why it failed.") } lookingAhead = false diff --git a/src/test/resources/regression/genc/unverified/IntegralColor.scala b/src/test/resources/regression/genc/unverified/IntegralColor.scala index 28e7a23d3..0e859266d 100644 --- a/src/test/resources/regression/genc/unverified/IntegralColor.scala +++ b/src/test/resources/regression/genc/unverified/IntegralColor.scala @@ -19,7 +19,7 @@ object IntegralColor { rgb & 0x000000FF } ensuring isValidComponent _ - def getGray(rgb: Int): Int = { + def getGrey(rgb: Int): Int = { (getRed(rgb) + getGreen(rgb) + getBlue(rgb)) / 3 } ensuring isValidComponent _ @@ -27,7 +27,7 @@ object IntegralColor { val color = 0x20C0FF 32 == getRed(color) && 192 == getGreen(color) && - 255 == getBlue(color) && 159 == getGray(color) + 255 == getBlue(color) && 159 == getGrey(color) }.holds case class Color(r: Int, g: Int, b: Int) @@ -35,10 +35,9 @@ object IntegralColor { def getColor(rgb: Int) = Color(getRed(rgb), getGreen(rgb), getBlue(rgb)) /* - *case class Image(width: Int, height: Int, buffer: Array[Int]) { - * // currently not enforced: - * require(width <= 1000 && height <= 1000 && buffer.length == width * height) - *} + * case class Image(width: Int, height: Int, buffer: Array[Int]) { + * require(width <= 1024 && height <= 1024 && buffer.length == width * height) + * } */ def matches(value: Array[Int], expected: Array[Int]): Boolean = { @@ -60,38 +59,38 @@ object IntegralColor { /* *val source = Image(WIDTH, HEIGHT, Array(0x20c0ff, 0x123456, 0xffffff, 0x000000)) - *val expected = Image(WIDTH, HEIGHT, Array(159, 52, 255, 0)) // gray convertion - *val gray = Image(WIDTH, HEIGHT, Array.fill(4)(0)) + *val expected = Image(WIDTH, HEIGHT, Array(159, 52, 255, 0)) // grey convertion + *val grey = Image(WIDTH, HEIGHT, Array.fill(4)(0)) */ val source = Array(0x20c0ff, 0x123456, 0xffffff, 0x000000) - val expected = Array(159, 52, 255, 0) // gray convertion - val gray = Array.fill(4)(0) + val expected = Array(159, 52, 255, 0) // grey convertion + val grey = Array.fill(4)(0) - // NOTE: Cannot define a toGray function as XLang doesn't allow mutating + // NOTE: Cannot define a toGrey function as XLang doesn't allow mutating // arguments and GenC doesn't allow returning arrays var idx = 0 (while (idx < WIDTH * HEIGHT) { - gray(idx) = getGray(source(idx)) + grey(idx) = getGrey(source(idx)) idx = idx + 1 - }) invariant { idx >= 0 && idx <= WIDTH * HEIGHT && gray.length == WIDTH * HEIGHT } + }) invariant { idx >= 0 && idx <= WIDTH * HEIGHT && grey.length == WIDTH * HEIGHT } // NB: the last invariant is very important -- without it the verification times out - matches(gray, expected) + matches(grey, expected) }.holds // Only for square kernels case class Kernel(size: Int, buffer: Array[Int]) def isKernelValid(kernel: Kernel): Boolean = - kernel.size > 0 && kernel.size < 1000 && kernel.size % 2 == 1 && + kernel.size > 0 && kernel.size < 1024 && kernel.size % 2 == 1 && kernel.buffer.length == kernel.size * kernel.size - def applyFilter(gray: Array[Int], size: Int, idx: Int, kernel: Kernel): Int = { - require(size > 0 && size < 1000 && - gray.length == size * size && - idx >= 0 && idx < gray.length && + def applyFilter(grey: Array[Int], size: Int, idx: Int, kernel: Kernel): Int = { + require(size > 0 && size < 1024 && + grey.length == size * size && + idx >= 0 && idx < grey.length && isKernelValid(kernel)) def up(x: Int): Int = { @@ -110,7 +109,7 @@ object IntegralColor { val r = fix(row) val c = fix(col) - gray(r * size + c) + grey(r * size + c) } val mid = kernel.size / 2 @@ -144,7 +143,7 @@ object IntegralColor { } def testFilterConvolutionSmooth: Boolean = { - val gray = Array(127, 255, 51, 0) + val grey = Array(127, 255, 51, 0) val expected = Array(124, 158, 76, 73) val size = 2 // grey is size x size @@ -155,16 +154,16 @@ object IntegralColor { 1, 2, 1, 1, 1, 1)) - // val smoothed = Array.fill(gray.length)(0) // This is a VLA - assert(gray.length == 4) + // val smoothed = Array.fill(grey.length)(0) // This is a VLA + assert(grey.length == 4) val smoothed = Array.fill(4)(0) assert(smoothed.length == expected.length) var idx = 0; (while (idx < smoothed.length) { - smoothed(idx) = applyFilter(gray, size, idx, kernel) / 10 + smoothed(idx) = applyFilter(grey, size, idx, kernel) / 10 idx = idx + 1 - }) invariant { idx >= 0 && idx <= smoothed.length && smoothed.length == gray.length } + }) invariant { idx >= 0 && idx <= smoothed.length && smoothed.length == grey.length } matches(smoothed, expected) }.holds From e27a4f74e3cbea6d65da954cd1f6bd27d2a511ce Mon Sep 17 00:00:00 2001 From: Marco Antognini Date: Fri, 27 Jan 2017 19:25:44 +0100 Subject: [PATCH 77/77] Add case study ImageProcessing for GenC Use comments to select the kernel you want to use, then recompile. --- testcases/genc/ImageProcessing.scala | 763 +++++++++++++++++++++++++++ 1 file changed, 763 insertions(+) create mode 100644 testcases/genc/ImageProcessing.scala diff --git a/testcases/genc/ImageProcessing.scala b/testcases/genc/ImageProcessing.scala new file mode 100644 index 000000000..6e08664a6 --- /dev/null +++ b/testcases/genc/ImageProcessing.scala @@ -0,0 +1,763 @@ +/* Copyright 2009-2016 EPFL, Lausanne */ + +import leon.annotation._ +import leon.lang._ + +import leon.io.{ + FileInputStream => FIS, + FileOutputStream => FOS, + StdOut +} + +import scala.annotation.tailrec + +/** + * Some basic image processing. + * + * General NOTEs + * ------------- + * + * Byte ranges from -128 to 127, not 0 to 255. It is important to remember + * that when manipulating individual component as Byte. + * + * The BMP format uses little endian. + * + * See https://msdn.microsoft.com/en-us/library/dd183391(v=vs.85).aspx + * for the full documentation. + */ +object ImageProcessing { + + /*************************************************************************** + * Constants * + ***************************************************************************/ + + // Sizes in bytes of several Windows numerical types + @inline + val WordSize = 2 // 16 bits, unsigned + @inline + val DwordSize = 4 // 32 bits, unsigned + @inline + val LongSize = 4 // 32 bits, signed + + // Maximum size of images + @inline + val MaxSize = 512 + @inline + val MaxSurfaceSize = 512 * 512 // handwritten here to inline the values + + + /*************************************************************************** + * Basic Algorithms * + ***************************************************************************/ + + def inRange(x: Int, min: Int, max: Int): Boolean = { + require(min <= max) + min <= x && x <= max + } + + def min(x: Int, y: Int): Int = { + if (x <= y) x else y + } ensuring { res => + res <= x && res <= y && (res == x || res == y) + } + + def max(x: Int, y: Int): Int = { + if (x < y) y else x + } ensuring { res => + x <= res && y <= res && (res == x || res == y) + } + + def clamp(x: Int, down: Int, up: Int): Int = { + require(down <= up) + max(down, min(x, up)) + } ensuring { res => inRange(res, down, up) } + + + /*************************************************************************** + * Status * + ***************************************************************************/ + + sealed abstract class Status { + def isSuccess: Boolean = this.isInstanceOf[Success] + } + + case class Success() extends Status + case class OpenError() extends Status + case class ReadError() extends Status + case class DomainError() extends Status + case class InvalidFileHeaderError() extends Status + case class InvalidBitmapHeaderError() extends Status + case class CorruptedDataError() extends Status + case class ImageTooBigError() extends Status + case class WriteError() extends Status + case class NotImplementedError() extends Status + + def statusCode(s: Status): Int = s match { + case Success() => StdOut.println("success"); 0 + case OpenError() => StdOut.println("couldn't open file"); 1 + case ReadError() => StdOut.println("couldn't read some expected data"); 2 + case DomainError() => StdOut.println("integer out of range"); 3 + case InvalidFileHeaderError() => StdOut.println("file format unsupported"); 4 + case InvalidBitmapHeaderError() => StdOut.println("bitmap format unsupported"); 5 + case CorruptedDataError() => StdOut.println("the file appears to be corrupted"); 6 + case ImageTooBigError() => StdOut.println("the image is too big"); 7 + case WriteError() => StdOut.println("couldn't write image"); 8 + case NotImplementedError() => StdOut.println("not yet implemented"); 99 + } + + + /*************************************************************************** + * MaybeResult * + ***************************************************************************/ + + // Basically, MaybeResult[A] is Either[A, B] where B is Status + abstract class MaybeResult[A] { + def isDefined = this match { + case Result(_) => true + case _ => false + } + + def getResult: A = { + require(isDefined) + this.asInstanceOf[Result[A]].result + } + + def getStatus: Status = { + require(!isDefined) + this.asInstanceOf[Failure[A]].status + } + + def toStatus: Status = { + if (isDefined) Success() + else getStatus + } + } + + case class Result[A](result: A) extends MaybeResult[A] + case class Failure[A](status: Status) extends MaybeResult[A] { + require(status != Success()) + } + + // Extra operations for MaybeResult[Int]. + implicit class MaybeResultIntOps(val result: MaybeResult[Int]) { + def expect(value: Int): MaybeResult[Int] = result match { + case Result(res) if res == value => result + case Result(_) => Failure[Int](DomainError()) + case _ => result // a Failure remains a Failure + } + } + + // Combine two, three or four MaybeResult to a MaybeResult of tuple. + def combine[A, B](a: MaybeResult[A], b: MaybeResult[B]): MaybeResult[(A, B)] = { + if (a.isDefined) { + if (b.isDefined) { + Result((a.getResult, b.getResult)) + } else Failure[(A, B)](b.getStatus) + } else Failure[(A, B)](a.getStatus) + } + + def combine[A, B, C](a: MaybeResult[A], b: MaybeResult[B], + c: MaybeResult[C]): MaybeResult[(A, B, C)] = { + val tmp = combine(combine(a, b), c) + tmp match { + case Result(((a, b), c)) => Result((a, b, c)) + case Failure(status) => Failure[(A, B, C)](status) + } + } + + def combine[A, B, C, D](a: MaybeResult[A], b: MaybeResult[B], + c: MaybeResult[C], d: MaybeResult[D]): MaybeResult[(A, B, C, D)] = { + val tmp = combine(combine(a, b, c), d) + tmp match { + case Result(((a, b, c), d)) => Result((a, b, c, d)) + case Failure(status) => Failure[(A, B, C, D)](status) + } + } + + // Convert an Option to a MaybeResult + def maybe[A](opt: Option[A], failStatus: Status): MaybeResult[A] = { + require(failStatus != Success()) + opt match { + case Some(result) => Result(result) + case None() => Failure(failStatus) + } + } + + // Special DSL for Option. + implicit class OptionOps[A](val opt: Option[A]) { + def toResultOr(failStatus: Status) = { + require(failStatus != Success()) + maybe(opt, failStatus) + } + } + + + /*************************************************************************** + * Data Structures * + ***************************************************************************/ + + /* + * Hold (some) information about the general file structure; + * The file header is 14 bytes, the offset refers to the beginning of the file header. + */ + case class FileHeader(size: Int, offset: Int) { + require((14 + 40) <= size && inRange(offset, 14 + 40, size)) + // offset cannot be before the end of BitmapHeader. + } + + /* + * Hold basic information about the bitmap. + * + * See https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx + * + * NOTE We assume that + * - The number of bits-per-pixel is 24 (RGB format, 8-bit channels); + * - No compression is used; + * - The palette is empty. + */ + case class BitmapHeader(width: Int, height: Int) { + require(0 <= width && 0 <= height) + } + + /* + * Represent an Image, using the usual RGB channels. + * + * NOTE use createImage to create a new instance of this class easily. + */ + case class Image(r: Array[Byte], g: Array[Byte], b: Array[Byte], w: Int, h: Int) { + require( + r.length == MaxSurfaceSize && + g.length == MaxSurfaceSize && + b.length == MaxSurfaceSize && + inRange(w, 0, MaxSize) && + inRange(h, 0, MaxSize) && + inRange(w * h, 0, MaxSurfaceSize) + ) + } + + @inline // <- in order to "return" the image + def createImage(width: Int, height: Int) = { + require( + inRange(width, 0, MaxSize) && + inRange(height, 0, MaxSize) && + inRange(width * height, 0, MaxSurfaceSize) + ) + Image( + Array.fill[Byte](MaxSurfaceSize)(0), + Array.fill[Byte](MaxSurfaceSize)(0), + Array.fill[Byte](MaxSurfaceSize)(0), + width, height + ) + } + + + /*************************************************************************** + * I/O functions for WORD, DWORD, LONG, and other helpers * + ***************************************************************************/ + + // Skip a given number of bytes, returning true on success. + def skipBytes(fis: FIS, count: Int)(implicit state: leon.io.State): Boolean = { + require(fis.isOpen && 0 <= count) + + var i = 0 + var success = true + + (while (success && i < count) { + val opt = fis.tryReadByte() + success = opt.isDefined + i += 1 + }) invariant (inRange(i, 0, count)) + + success + } + + // Fill the output with copies of the given byte. + @tailrec // <- a good indicator that the C compiler could optimise out the recursion. + def writeBytes(fos: FOS, byte: Byte, count: Int): Boolean = { + require(fos.isOpen && 0 <= count) + + if (count == 0) true + else fos.write(byte) && writeBytes(fos, byte, count - 1) + } + + // Attempt to read a WORD (16-bit unsigned integer). + // The result is represented using an Int. + def maybeReadWord(fis: FIS)(implicit state: leon.io.State): MaybeResult[Int] = { + require(fis.isOpen) + + // From little to big endian + def buildShort(b1: Byte, b2: Byte): Int = { + (b2 << 8) | (b1 & 0xff) // has Int type + } ensuring { short => + inRange(short, -32768, 32767) + } + + val byte1 = fis.tryReadByte + val byte2 = fis.tryReadByte + + if (byte1.isDefined && byte2.isDefined) { + // Shift range appropriately to respect unsigned numbers representation + val signed = buildShort(byte1.get, byte2.get) + val unsigned = if (signed < 0) signed + 65536 else signed + Result(unsigned) + } else Failure[Int](ReadError()) + } ensuring { res => + res match { + case Result(word) => inRange(word, 0, 65536) + case _ => true + } + } + + // Write a WORD + def writeWord(fos: FOS, word: Int): Boolean = { + require(fos.isOpen && inRange(word, 0, 65536)) + + // Shift range appropriatedly to respect integer representation + val signed = if (word >= 32768) word - 32768 else word + + val b2 = (signed >>> 8).toByte + val b1 = signed.toByte + + // Convert big endian to little endian + fos.write(b1) && fos.write(b2) + } + + // Attempt to read a DWORD (32-bit unsigned integer). + // The result is represented using an Int, and values bigger than 2^31 results in DomainError. + def maybeReadDword(fis: FIS)(implicit state: leon.io.State): MaybeResult[Int] = { + require(fis.isOpen) + + // From little to big endian + def buildInt(b1: Byte, b2: Byte, b3: Byte, b4: Byte): Int = { + require(0 <= b4) + (b4 << 24) | ((b3 & 0xff) << 16) | ((b2 & 0xff) << 8) | (b1 & 0xff) + } ensuring { int => + inRange(int, 0, 2147483647) + } + + val byte1 = fis.tryReadByte + val byte2 = fis.tryReadByte + val byte3 = fis.tryReadByte + val byte4 = fis.tryReadByte // the most significant byte + + if (byte1.isDefined && byte2.isDefined && byte3.isDefined && byte4.isDefined) { + if (byte4.get >= 0) { + val dword = buildInt(byte1.get, byte2.get, byte3.get, byte4.get) + Result(dword) + } else Failure[Int](DomainError()) + } else Failure[Int](ReadError()) + } ensuring { res => + res match { + case Result(dword) => inRange(dword, 0, 2147483647) + case _ => true + } + } + + // Write a DWORD + def writeDword(fos: FOS, dword: Int): Boolean = { + require(fos.isOpen && inRange(dword, 0, 2147483647)) + + val b4 = (dword >>> 24).toByte + val b3 = (dword >>> 16).toByte + val b2 = (dword >>> 8).toByte + val b1 = dword.toByte + + // Big endian to little endian conversion + fos.write(b1) && fos.write(b2) && fos.write(b3) && fos.write(b4) + } + + // Attempt to read a LONG (32-bit signed integer). + // The result is represented using an Int. + def maybeReadLong(fis: FIS)(implicit state: leon.io.State): MaybeResult[Int] = { + require(fis.isOpen) + + // From little to big endian + def buildInt(b1: Byte, b2: Byte, b3: Byte, b4: Byte): Int = { + (b4 << 24) | ((b3 & 0xff) << 16) | ((b2 & 0xff) << 8) | (b1 & 0xff) + } + + val byte1 = fis.tryReadByte + val byte2 = fis.tryReadByte + val byte3 = fis.tryReadByte + val byte4 = fis.tryReadByte // the most significant byte + + if (byte1.isDefined && byte2.isDefined && byte3.isDefined && byte4.isDefined) { + val long = buildInt(byte1.get, byte2.get, byte3.get, byte4.get) + Result(long) + } else Failure[Int](ReadError()) + } + + // Write a LONG + def writeLong(fos: FOS, long: Int): Boolean = { + require(fos.isOpen) + + val b4 = (long >>> 24).toByte + val b3 = (long >>> 16).toByte + val b2 = (long >>> 8).toByte + val b1 = long.toByte + + // Big endian to little endian conversion + fos.write(b1) && fos.write(b2) && fos.write(b3) && fos.write(b4) + } + + + /*************************************************************************** + * I/O functions for the BMP format * + ***************************************************************************/ + + // Attempt to read the file header. + // Upon success, 14 bytes have been read. + def maybeReadFileHeader(fis: FIS)(implicit state: leon.io.State): MaybeResult[FileHeader] = { + require(fis.isOpen) + + var skipSuccess = skipBytes(fis, WordSize) + val sizeRes = maybeReadDword(fis) + skipSuccess = skipSuccess && skipBytes(fis, WordSize * 2) + val offsetRes = maybeReadDword(fis) + + combine(sizeRes, offsetRes) match { + case _ if !skipSuccess => Failure[FileHeader](ReadError()) + case Failure(status) => Failure[FileHeader](status) + case Result((size, offset)) => { + if (14 <= size && 14 + 40 <= offset && offset <= size) Result(FileHeader(size, offset)) + else Failure[FileHeader](InvalidFileHeaderError()) + } + } + } + + // Attempt to read the bitmap header (minimal version). + // Upon success, 18 bytes have been read. + def maybeReadBitmapHeader(fis: FIS)(implicit state: leon.io.State): MaybeResult[BitmapHeader] = { + require(fis.isOpen) + + var skipSuccess = skipBytes(fis, DwordSize) + val widthRes = maybeReadLong(fis) + val heightRes = maybeReadLong(fis) + skipSuccess = skipSuccess && skipBytes(fis, WordSize) + val bppRes = maybeReadWord(fis) + val compressionRes = maybeReadWord(fis) + + combine(widthRes, heightRes, bppRes, compressionRes) match { + case _ if !skipSuccess => Failure[BitmapHeader](ReadError()) + case Failure(status) => Failure[BitmapHeader](status) + case Result((w, h, bpp, compression)) => + if (w < 0 || h < 0 || bpp != 24 || compression != 0) { + log("width", w) + log("height", h) + log("bpp", bpp) + log("compression", compression) + Failure(InvalidBitmapHeaderError()) + } else Result(BitmapHeader(w, h)) + } + } + + def loadImageData(fis: FIS, image: Image)(implicit state: leon.io.State): Status = { + require(fis.isOpen) + + val size = image.w * image.h + var i = 0 + var status: Status = Success() + + (while (status.isSuccess && i < size) { + val rOpt = fis.tryReadByte() + val gOpt = fis.tryReadByte() + val bOpt = fis.tryReadByte() + + if (rOpt.isEmpty || gOpt.isEmpty || bOpt.isEmpty) { + status = ReadError() + log("stopped reading data abruptly after", i) + } else { + image.r(i) = rOpt.get + image.g(i) = gOpt.get + image.b(i) = bOpt.get + } + + i += 1 + }) invariant ( + inRange(size, 0, MaxSurfaceSize) && + inRange(i, 0, size) + ) + + status + } + + def saveImage(fos: FOS, image: Image): Status = { + require(fos.isOpen) + + def writeFileHeader(): Boolean = { + // Size: the headers and 3 channels per pixel, 1 byte per pixel component. + val size = 14 + 40 + image.w * image.h * 3 + val reserved = 0 // two WORDs are reserved + val offset = 14 + 40 // after the two headers + + fos.write(0x42.toByte) && fos.write(0x4d.toByte) && // the signature "BM" + writeDword(fos, size) && + writeWord(fos, reserved) && writeWord(fos, reserved) && + writeDword(fos, offset) + } + + def writeBitmapHeader(): Boolean = { + val size = 40 + val w = image.w + val h = image.h + val planes = 1 + val bpp = 24 + val comp = 0 + + writeDword(fos, size) && + writeLong(fos, w) && writeLong(fos, h) && + writeWord(fos, planes) && + writeWord(fos, bpp) && + writeWord(fos, comp) && + writeBytes(fos, 0, 22) // the last 22 bytes are all not relevant for us and are set to 0 + } + + def writeImage(): Boolean = { + val count = image.w * image.h + var i = 0 + var success = true + + (while (success && i < count) { + success = fos.write(image.r(i)) && fos.write(image.g(i)) && fos.write(image.b(i)) + i += 1 + }) invariant (inRange(count, 0, MaxSurfaceSize) && inRange(i, 0, count)) + + success + } + + if (writeFileHeader() && writeBitmapHeader() && writeImage()) Success() + else WriteError() + } + + + /*************************************************************************** + * Logging Facilities * + ***************************************************************************/ + + def log(msg: String, x: Int) { + StdOut.print(msg) + StdOut.print(": ") + StdOut.println(x) + } + + def log(h: FileHeader) { + log("size", h.size) + log("offset", h.offset) + } + + def log(h: BitmapHeader) { + log("width", h.width) + log("height", h.height) + } + + + /*************************************************************************** + * Kernel & Image Processing Algorithm * + ***************************************************************************/ + + case class Kernel(size: Int, scale: Int, kernel: Array[Int]) { + require( + inRange(size, 0, MaxSize) && + size % 2 == 1 && + size * size == kernel.length && + scale != 0 && scale != -1 // avoid division by zero and some particular overflow (*) + ) + + // (*) -2^31 / -1 + + /* + * Apply the kernel on the given channel. Return the new value for pixel component + * at the given index. + */ + private def apply(channel: Array[Byte], width: Int, height: Int, index: Int): Byte = { + require( + channel.length == MaxSurfaceSize && + inRange(index, 0, channel.length) && + inRange(width, 1, MaxSize) && + inRange(height, 1, MaxSize) && + inRange(width * height, 0, MaxSurfaceSize) + ) + + // Clamping helper + def fix(x: Int, side: Int): Int = { + require(0 < side) + clamp(x, 0, side - 1) + } + + // Get the color component at the given position in the range [0, 255] + def at(col: Int, row: Int): Int = { + val c = fix(col, width) + val r = fix(row, height) + + val component = channel(r * width + c) // unsigned + if (component < 0) component + 255 else component + } ensuring { inRange(_, 0, 255) } + + val mid = size / 2 + + val i = index % width + val j = index / width + + var res = 0 + var p = -mid + + (while (p <= mid) { + var q = -mid + + val oldP = p // Fix p for the inner loop (the invariant is not automatically inferred) + (while (q <= mid) { + val kcol = p + mid + val krow = q + mid + + assert(inRange(krow, 0, size - 1)) + assert(inRange(kcol, 0, size - 1)) + + val kidx = krow * size + kcol + + // Here, the += and * operation could overflow + res += at(i + p, j + q) * kernel(kidx) + + q += 1 + }) invariant (oldP == p && inRange(q, -mid, mid + 1)) + + p += 1 + }) invariant (inRange(p, -mid, mid + 1)) + + res = clamp(res / scale, 0, 255) + res.toByte + } + + def apply(src: Image, dest: Image): Unit = { + require(src.w == dest.w && src.h == dest.h) + + val size = src.w * src.h + var i = 0 + + (while (i < size) { + dest.r(i) = apply(src.r, src.w, src.h, i) + dest.g(i) = apply(src.g, src.w, src.h, i) + dest.b(i) = apply(src.b, src.w, src.h, i) + + i += 1 + }) invariant (inRange(i, 0, size)) + } + } + + + /*************************************************************************** + * Main Program * + ***************************************************************************/ + + @extern + def main(args: Array[String]): Unit = _main() + + def _main(): Int = { + implicit val state = leon.io.newState + val input = FIS.open("input.bmp") + val output = FOS.open("output.bmp") + + val status = + if (input.isOpen && output.isOpen) process(input, output) + else OpenError() + + output.close() + input.close() + + statusCode(status) + } + + def process(fis: FIS, fos: FOS)(implicit state: leon.io.State): Status = { + require(fis.isOpen && fos.isOpen) + + /* + * // Smooth kernel + * val kernel = Kernel(3, 1, Array(1, 1, 1, 1, 2, 1, 1, 1, 1)) + */ + + /* // Edges + * val kernel = Kernel(5, 1, Array( + * 0, 0, -1, 0, 0, + * 0, 0, -1, 0, 0, + * -1, -1, 8, -1, -1, + * 0, 0, -1, 0, 0, + * 0, 0, -1, 0, 0 + * )) + */ + + /* // Identity + * val kernel = Kernel(5, 1, Array( + * 0, 0, 0, 0, 0, + * 0, 0, 0, 0, 0, + * 0, 0, 1, 0, 0, + * 0, 0, 0, 0, 0, + * 0, 0, 0, 0, 0 + * )) + */ + + /* // Sharpen + * val kernel = Kernel(5, 8, Array( + * -1, -1, -1, -1, -1, + * -1, 2, 2, 2, -1, + * -1, 2, 8, 2, -1, + * -1, 2, 2, 2, -1, + * -1, -1, -1, -1, -1 + * )) + */ + + // Blur + val kernel = Kernel(5, 25, Array( + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + )) + + + def processImage(src: Image): Status = { + val dest = createImage(src.w, src.h) + kernel.apply(src, dest) + saveImage(fos, dest) + } + + val fileHeaderRes = maybeReadFileHeader(fis) + val bitmapHeaderRes = maybeReadBitmapHeader(fis) + + val status = combine(fileHeaderRes, bitmapHeaderRes) match { + case Failure(status) => + status + + /* + * Report an error when the file is corrupted, i.e. it's too small. + * 40 is the minimal bitmap header size, 14 is the file header size. + * Note that more sanity check could be done but that's not the main + * point of this example. + */ + case Result((fh, bh)) if fh.size <= 14 + 40 => + CorruptedDataError() + + case Result((fh, bh)) => + log(fh) + log(bh) + + // Skip bytes until the start of the bitmap data + val toSkip = fh.offset - (14 + 18) // some bytes were already eaten + val success = skipBytes(fis, toSkip) + + // Break test of size so we avoid overflows. + if (!success) CorruptedDataError() + else if (bh.width > MaxSize || bh.height > MaxSize) ImageTooBigError() + else if (bh.width * bh.height > MaxSurfaceSize) ImageTooBigError() + else { + val image = createImage(bh.width, bh.height) + val status = loadImageData(fis, image) + if (status.isSuccess) processImage(image) + else status + } + } + + status + } + +} +