From 1142d190be7541f266e7492a9cc5a2442b50f687 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 16:31:36 +0530 Subject: [PATCH 001/119] Initial commit on branch modelcounting. --- src/main/scala/uclid/UclidMain.scala | 10 +++++++++- src/main/scala/uclid/lang/modelcounts/UMCMain.scala | 7 +++++++ src/main/scala/uclid/lang/modelcounts/UMCParser.scala | 5 +++++ test/modelcounter/hello.ucl | 6 ++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/uclid/lang/modelcounts/UMCMain.scala create mode 100644 src/main/scala/uclid/lang/modelcounts/UMCParser.scala create mode 100644 test/modelcounter/hello.ucl diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 7a44fac57..e914352de 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -78,7 +78,8 @@ object UclidMain { printStackTrace: Boolean = false, verbose : Int = 0, files : Seq[java.io.File] = Seq(), - testFixedpoint: Boolean = false + testFixedpoint: Boolean = false, + modelCounter: Boolean = false ) def parseOptions(args: Array[String]) : Option[Config] = { @@ -120,6 +121,10 @@ object UclidMain { opt[Unit]('t', "test-fixedpoint").action { (_, c) => c.copy(testFixedpoint = true) }.text("Test fixed point") + + opt[Unit]('C', "model-counter").action { + (_, c) => c.copy(modelCounter = true) + }.text("Model counter DSL.") help("help").text("prints this usage text") @@ -140,6 +145,9 @@ object UclidMain { smt.Z3HornSolver.test1() return } + if (config.modelCounter) { + config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + } val mainModuleName = Identifier(config.mainModuleName) val modules = compile(config.files, mainModuleName) val mainModule = instantiate(config, modules, mainModuleName, true) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala new file mode 100644 index 000000000..cfcb0767d --- /dev/null +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -0,0 +1,7 @@ +package uclid.lang.modelcounts + +object UMCMain { + def checkModel(f: java.io.File) { + println("Model counter invoked.") + } +} \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala new file mode 100644 index 000000000..9a17aa2eb --- /dev/null +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -0,0 +1,5 @@ +package uclid.lang.modelcounts + +class UMCParser { + +} \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl new file mode 100644 index 000000000..f52e0c535 --- /dev/null +++ b/test/modelcounter/hello.ucl @@ -0,0 +1,6 @@ +define f(n : integer) : boolean = n >= 5 && n <= 10; +define g(n : integer) : boolean = n >= 11 && n <= 20; +define h(n : integer) : boolean = n >= 5 && n <= 20; + +assert #SAT[n](f(n) && g(n)) < 1; + From 97672c9010a04055675e6f1ec32e38f7dc237c64 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 18:00:08 +0530 Subject: [PATCH 002/119] starting to parse the model counter files. --- src/main/scala/uclid/lang/UclidLanguage.scala | 2 +- src/main/scala/uclid/lang/UclidParser.scala | 10 +++++- .../uclid/lang/modelcounts/UMCMain.scala | 3 +- .../uclid/lang/modelcounts/UMCParser.scala | 34 +++++++++++++++++-- test/modelcounter/hello.ucl | 11 +++--- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 75498a9a4..b7cc01d3d 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -135,7 +135,7 @@ object ASTNode { /** All elements in the AST are derived from this class. * The plan is to stick an ID into this later so that we can use the ID to store auxiliary information. */ -sealed trait ASTNode extends Positional with PositionedNode { +trait ASTNode extends Positional with PositionedNode { val astNodeId = IdGenerator.newId() } diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 9b8d53249..884f9410e 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -83,7 +83,7 @@ trait UclidTokenParsers extends TokenParsers { elem("identifier", _.isInstanceOf[Identifier]) ^^ (_.chars) } -object UclidParser extends UclidTokenParsers with PackratParsers { +class UclidParser extends UclidTokenParsers with PackratParsers { type Tokens = UclidTokens val lexical = new UclidLexical @@ -804,4 +804,12 @@ object UclidParser extends UclidTokenParsers with PackratParsers { case NoSuccess(msg, next) => throw new Utils.SyntaxError(msg, Some(next.pos), Some(filename)) } } +} + +object UclidParser { + val parserObj = new UclidParser() + def parseModel(filename: String, text: String): List[Module] = { + parserObj.parseModel(filename, text) } +} + \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index cfcb0767d..7f92db412 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -2,6 +2,7 @@ package uclid.lang.modelcounts object UMCMain { def checkModel(f: java.io.File) { - println("Model counter invoked.") + val module = UMCParser.parseUMCModel(f) + println(module.toString()) } } \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 9a17aa2eb..659fffc5d 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,5 +1,35 @@ package uclid.lang.modelcounts -class UMCParser { - +import uclid.lang.{Decl, Module, UclidParser} + +class UMCParser extends UclidParser { + lazy val UMCDecl: PackratParser[Decl] = + positioned (TypeDecl | DefineDecl) + + lazy val UMCModule: PackratParser[Module] = positioned { + KwModule ~> Id ~ ("{" ~> rep(UMCDecl) <~ "}") ^^ { + case id ~ decls => uclid.lang.Module(id, decls, List.empty, List.empty) + } + } + + def parseUMCModel(filename : String, text: String): Module = { + val tokens = new PackratReader(new lexical.Scanner(text)) + phrase(UMCModule)(tokens) match { + case Success(module, _) => module + case NoSuccess(msg, next) => throw new uclid.Utils.SyntaxError(msg, Some(next.pos), Some(filename)) + } + } +} + +object UMCParser { + val parserObj = new UMCParser() + val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) + def parseUMCModel(file: java.io.File) : Module = { + val filePath = file.getPath() + val text = scala.io.Source.fromFile(filePath).mkString + filenameAdderPass.setFilename(filePath) + filenameAdderPass.visit( + parserObj.parseUMCModel(filePath, text), + uclid.lang.Scope.empty).get + } } \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index f52e0c535..68232a0b1 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,6 +1,9 @@ -define f(n : integer) : boolean = n >= 5 && n <= 10; -define g(n : integer) : boolean = n >= 11 && n <= 20; -define h(n : integer) : boolean = n >= 5 && n <= 20; +module main { -assert #SAT[n](f(n) && g(n)) < 1; + define f(n : integer) : boolean = n >= 5 && n <= 10; + define g(n : integer) : boolean = n >= 11 && n <= 20; + define h(n : integer) : boolean = n >= 5 && n <= 20; + + // assert #SAT[n](f(n) && g(n)) < 1; +} From a1d02e7a3065ff61b7c4fa4694be743aedf46fcb Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 23:09:42 +0530 Subject: [PATCH 003/119] parser is beginning to work. --- src/main/scala/uclid/UclidMain.scala | 1 + .../uclid/lang/modelcounts/UMCParser.scala | 31 ++++++++++++++----- test/modelcounter/hello.ucl | 4 ++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index e914352de..2dc403a0a 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -147,6 +147,7 @@ object UclidMain { } if (config.modelCounter) { config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + return } val mainModuleName = Identifier(config.mainModuleName) val modules = compile(config.files, mainModuleName) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 659fffc5d..365bbba92 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,18 +1,33 @@ package uclid.lang.modelcounts -import uclid.lang.{Decl, Module, UclidParser} +import uclid.{lang => l} -class UMCParser extends UclidParser { - lazy val UMCDecl: PackratParser[Decl] = + +class UMCParser extends l.UclidParser { + lazy val KwProof = "proof" + lexical.reserved += (KwProof) + + lazy val UMCDecl: PackratParser[uclid.lang.Decl] = positioned (TypeDecl | DefineDecl) - lazy val UMCModule: PackratParser[Module] = positioned { - KwModule ~> Id ~ ("{" ~> rep(UMCDecl) <~ "}") ^^ { - case id ~ decls => uclid.lang.Module(id, decls, List.empty, List.empty) + lazy val AssertStmt: PackratParser[uclid.lang.Statement] = positioned { + KwAssert ~> Expr <~ ";" ^^ { case e => l.AssertStmt(e, None) } + } + lazy val ProofStmt: PackratParser[l.Statement] = + positioned ( AssertStmt ); + lazy val ProofScript: PackratParser[List[l.Statement]] = { + KwProof ~ "{" ~> rep(ProofStmt) <~ "}" + } + lazy val UMCModule: PackratParser[l.Module] = positioned { + KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { + case id ~ decls ~ proof => { + val next = l.NextDecl(l.BlockStmt(List.empty, proof)) + uclid.lang.Module(id, next :: decls, List.empty, List.empty) + } } } - def parseUMCModel(filename : String, text: String): Module = { + def parseUMCModel(filename : String, text: String): l.Module = { val tokens = new PackratReader(new lexical.Scanner(text)) phrase(UMCModule)(tokens) match { case Success(module, _) => module @@ -24,7 +39,7 @@ class UMCParser extends UclidParser { object UMCParser { val parserObj = new UMCParser() val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) - def parseUMCModel(file: java.io.File) : Module = { + def parseUMCModel(file: java.io.File) : l.Module = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString filenameAdderPass.setFilename(filePath) diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 68232a0b1..f26a81595 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -4,6 +4,8 @@ module main { define g(n : integer) : boolean = n >= 11 && n <= 20; define h(n : integer) : boolean = n >= 5 && n <= 20; - // assert #SAT[n](f(n) && g(n)) < 1; + proof { + assert h(n) <==> (f(n) || g(n)); + } } From c5db3a9476ee8949baa9f18e8303f217e23b755a Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 4 Nov 2019 05:03:08 +0530 Subject: [PATCH 004/119] some more progress on the model counter. --- src/main/scala/uclid/UclidMain.scala | 2 +- .../uclid/lang/modelcounts/UMCMain.scala | 50 +++++++++++++++++-- test/modelcounter/hello.ucl | 2 +- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 2dc403a0a..b6e7a9099 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -146,7 +146,7 @@ object UclidMain { return } if (config.modelCounter) { - config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f, config)) return } val mainModuleName = Identifier(config.mainModuleName) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index 7f92db412..3ae364b0d 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -1,8 +1,52 @@ package uclid.lang.modelcounts +import uclid.UclidMain +import uclid.{lang => l} + object UMCMain { - def checkModel(f: java.io.File) { - val module = UMCParser.parseUMCModel(f) - println(module.toString()) + def checkModel(f: java.io.File, config: UclidMain.Config) { + val passManager = createModulePassManager(l.Identifier(config.mainModuleName)) + passManager.run(UMCParser.parseUMCModel(f), l.Scope.empty) match { + case Some(m) => UclidMain.execute(m, config) + case None => + throw new uclid.Utils.ParserError( + "Unable to find main module: " + config.mainModuleName, None, None) + } } + def createModulePassManager(mainModuleName: l.Identifier) = { + val passManager = new l.PassManager("compile") + // passManager.addPass(new ASTPrinter()) + passManager.addPass(new l.ModuleCanonicalizer()) + passManager.addPass(new l.ModuleTypesImportCollector()) + passManager.addPass(new l.ModuleConstantsImportCollector()) + passManager.addPass(new l.ModuleFunctionsImportCollector()) + + passManager.addPass(new l.ExternalTypeAnalysis()) + passManager.addPass(new l.ExternalTypeRewriter()) + passManager.addPass(new l.FuncExprRewriter()) + passManager.addPass(new l.InstanceModuleNameChecker()) + passManager.addPass(new l.InstanceModuleTypeRewriter()) + passManager.addPass(new l.RewritePolymorphicSelect()) + passManager.addPass(new l.ConstantLitRewriter()) + passManager.addPass(new l.TypeSynonymFinder()) + passManager.addPass(new l.TypeSynonymRewriter()) + passManager.addPass(new l.BlockVariableRenamer()) + passManager.addPass(new l.BitVectorSliceFindWidth()) + passManager.addPass(new l.ExpressionTypeChecker()) + passManager.addPass(new l.VerificationExpressionChecker()) + passManager.addPass(new l.PolymorphicTypeRewriter()) + passManager.addPass(new l.FindProcedureDependency()) + passManager.addPass(new l.DefDepGraphChecker()) + passManager.addPass(new l.RewriteDefines()) + passManager.addPass(new l.ModuleTypeChecker()) + passManager.addPass(new l.SemanticAnalyzer()) + passManager.addPass(new l.ControlCommandChecker()) + passManager.addPass(new l.ComputeInstanceTypes()) + passManager.addPass(new l.ModuleInstanceChecker()) + passManager.addPass(new l.BlockFlattener()) + passManager.addPass(new l.ModuleCleaner(mainModuleName)) + passManager.addPass(new l.BlockVariableRenamer()) + passManager + } + } \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index f26a81595..df415a287 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert h(n) <==> (f(n) || g(n)); + assert (forall (n : integer) :: h(n) <==> (f(n) || g(n))); } } From eff52b244a9c0f7f0b96202ddc76b8ac98594940 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 4 Nov 2019 16:15:50 +0530 Subject: [PATCH 005/119] first baby proof is working. --- .../uclid/lang/modelcounts/UMCParser.scala | 36 +++++++++++++++---- test/modelcounter/hello.ucl | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 365bbba92..c6b27f68f 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,28 +1,50 @@ package uclid.lang.modelcounts import uclid.{lang => l} +import uclid.lang.Identifier +import uclid.smt.IntLit class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lexical.reserved += (KwProof) + lazy val ControlBlock : List[l.GenericProofCommand] = List( + l.GenericProofCommand( + l.Identifier("unroll"), + List.empty, List((l.IntLit(1), "1")), + Some(l.Identifier("v")), None), + l.GenericProofCommand( + l.Identifier("check"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_results"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_cex"), + List.empty, List.empty, + None, Some(l.Identifier("v"))) + ) + lazy val UMCDecl: PackratParser[uclid.lang.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertStmt: PackratParser[uclid.lang.Statement] = positioned { - KwAssert ~> Expr <~ ";" ^^ { case e => l.AssertStmt(e, None) } + lazy val AssertDecl: PackratParser[uclid.lang.SpecDecl] = positioned { + KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { + case id ~ e => l.SpecDecl(id, e, List.empty) + } } - lazy val ProofStmt: PackratParser[l.Statement] = - positioned ( AssertStmt ); - lazy val ProofScript: PackratParser[List[l.Statement]] = { + lazy val ProofStmt: PackratParser[l.Decl] = + positioned ( AssertDecl ); + lazy val ProofScript: PackratParser[List[l.Decl]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - val next = l.NextDecl(l.BlockStmt(List.empty, proof)) - uclid.lang.Module(id, next :: decls, List.empty, List.empty) + uclid.lang.Module(id, decls ++ proof, ControlBlock, List.empty) } } } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index df415a287..4993789ee 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert (forall (n : integer) :: h(n) <==> (f(n) || g(n))); + assert or: (forall (n : integer) :: h(n) <==> (f(n) || g(n))); } } From 998f3b6da66ee80d7f40499d3fa43e0fb58874ed Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Tue, 5 Nov 2019 08:58:18 +0530 Subject: [PATCH 006/119] intermediate commit. --- src/main/scala/uclid/lang/UclidLanguage.scala | 8 ++++++++ src/main/scala/uclid/lang/UclidParser.scala | 7 ++++++- test/modelcounter/hello.ucl | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index b7cc01d3d..858c3ac0d 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -449,6 +449,14 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } +case class CountingOp(vs: List[(Identifier, Type)]) extends QuantifiedBooleanOperator { + override def variables = vs + override def toString() = "#[" + + Utils.join(vs.map(_.toString()), ", ") + "]" + override def fixity = Operator.PREFIX + override val hashId = 1402 + override val md5hashCode = computeMD5Hash(vs) +} // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 884f9410e..5d22b0da0 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -180,7 +180,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { // lazy val TemporalOpWUntil = "W" // lazy val TemporalOpRelease = "R" - lexical.delimiters ++= List("(", ")", ",", "[", "]", + lexical.delimiters ++= List("(", ")", ",", "[", "]", "#[", "bv", "{", "}", ";", "=", ":", "::", ".", "*", "::=", "->", OpAnd, OpOr, OpBvAnd, OpBvOr, OpBvXor, OpBvNot, OpAdd, OpSub, OpMul, OpBiImpl, OpImpl, OpLT, OpGT, OpLE, OpGE, OpULT, OpUGT, OpULE, OpUGE, @@ -373,6 +373,11 @@ class UclidParser extends UclidTokenParsers with PackratParsers { } | ConstArray | KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | + "#[" ~> IdTypeList ~ ( "]" ~ ":" ~ "(" ~> Expr <~ ")" ) ^^ { + case ids ~ expr => { + OperatorApplication(lang.CountingOp(ids), List(expr)) + } + } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | Id diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 4993789ee..7186ad025 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: (forall (n : integer) :: h(n) <==> (f(n) || g(n))); + assert or: #[n]:(f(n)) == #[n]:(g(n)) + #[n]:(h(n)); } } From 3cf2f070ff4cc65ff172ec43a48a656dd72eac73 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Tue, 7 Apr 2020 23:48:21 +0530 Subject: [PATCH 007/119] Merge with master after all recent commits. --- .../uclid/lang/modelcounts/UMCMain.scala | 47 ++----------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index 3ae364b0d..939ba78fd 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -5,48 +5,7 @@ import uclid.{lang => l} object UMCMain { def checkModel(f: java.io.File, config: UclidMain.Config) { - val passManager = createModulePassManager(l.Identifier(config.mainModuleName)) - passManager.run(UMCParser.parseUMCModel(f), l.Scope.empty) match { - case Some(m) => UclidMain.execute(m, config) - case None => - throw new uclid.Utils.ParserError( - "Unable to find main module: " + config.mainModuleName, None, None) - } + val module = UMCParser.parseUMCModel(f) + println("Managed to parse module: " + module.id.toString()) } - def createModulePassManager(mainModuleName: l.Identifier) = { - val passManager = new l.PassManager("compile") - // passManager.addPass(new ASTPrinter()) - passManager.addPass(new l.ModuleCanonicalizer()) - passManager.addPass(new l.ModuleTypesImportCollector()) - passManager.addPass(new l.ModuleConstantsImportCollector()) - passManager.addPass(new l.ModuleFunctionsImportCollector()) - - passManager.addPass(new l.ExternalTypeAnalysis()) - passManager.addPass(new l.ExternalTypeRewriter()) - passManager.addPass(new l.FuncExprRewriter()) - passManager.addPass(new l.InstanceModuleNameChecker()) - passManager.addPass(new l.InstanceModuleTypeRewriter()) - passManager.addPass(new l.RewritePolymorphicSelect()) - passManager.addPass(new l.ConstantLitRewriter()) - passManager.addPass(new l.TypeSynonymFinder()) - passManager.addPass(new l.TypeSynonymRewriter()) - passManager.addPass(new l.BlockVariableRenamer()) - passManager.addPass(new l.BitVectorSliceFindWidth()) - passManager.addPass(new l.ExpressionTypeChecker()) - passManager.addPass(new l.VerificationExpressionChecker()) - passManager.addPass(new l.PolymorphicTypeRewriter()) - passManager.addPass(new l.FindProcedureDependency()) - passManager.addPass(new l.DefDepGraphChecker()) - passManager.addPass(new l.RewriteDefines()) - passManager.addPass(new l.ModuleTypeChecker()) - passManager.addPass(new l.SemanticAnalyzer()) - passManager.addPass(new l.ControlCommandChecker()) - passManager.addPass(new l.ComputeInstanceTypes()) - passManager.addPass(new l.ModuleInstanceChecker()) - passManager.addPass(new l.BlockFlattener()) - passManager.addPass(new l.ModuleCleaner(mainModuleName)) - passManager.addPass(new l.BlockVariableRenamer()) - passManager - } - -} \ No newline at end of file +} From 33da53fc44155c7261a1a023d9586fb368d6f8d3 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 8 Apr 2020 00:10:36 +0530 Subject: [PATCH 008/119] Moving files for modelcounter into extensions. --- .../scala/uclid/{lang => extensions}/modelcounts/UMCMain.scala | 0 .../scala/uclid/{lang => extensions}/modelcounts/UMCParser.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/main/scala/uclid/{lang => extensions}/modelcounts/UMCMain.scala (100%) rename src/main/scala/uclid/{lang => extensions}/modelcounts/UMCParser.scala (100%) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala similarity index 100% rename from src/main/scala/uclid/lang/modelcounts/UMCMain.scala rename to src/main/scala/uclid/extensions/modelcounts/UMCMain.scala diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala similarity index 100% rename from src/main/scala/uclid/lang/modelcounts/UMCParser.scala rename to src/main/scala/uclid/extensions/modelcounts/UMCParser.scala From 2189edc55e5873fb3929e37cf4a4dd84bdbee121 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 8 Apr 2020 01:08:29 +0530 Subject: [PATCH 009/119] The toy file is being parsed. --- .../extensions/modelcounts/UMCMain.scala | 38 ++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 51 ++++++++++++++++--- src/main/scala/uclid/lang/UclidLanguage.scala | 7 ++- src/main/scala/uclid/lang/UclidParser.scala | 10 ++-- test/modelcounter/hello.ucl | 3 +- 5 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 939ba78fd..ea3609b31 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -1,3 +1,41 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Main file for the UCLID model counter. + * + */ package uclid.lang.modelcounts import uclid.UclidMain diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index c6b27f68f..18022fabd 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -1,3 +1,42 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Parser for the UCLID model counter. + * + */ + package uclid.lang.modelcounts import uclid.{lang => l} @@ -28,10 +67,10 @@ class UMCParser extends l.UclidParser { None, Some(l.Identifier("v"))) ) - lazy val UMCDecl: PackratParser[uclid.lang.Decl] = + lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertDecl: PackratParser[uclid.lang.SpecDecl] = positioned { + lazy val AssertDecl: PackratParser[l.SpecDecl] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { case id ~ e => l.SpecDecl(id, e, List.empty) } @@ -44,7 +83,7 @@ class UMCParser extends l.UclidParser { lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - uclid.lang.Module(id, decls ++ proof, ControlBlock, List.empty) + l.Module(id, decls ++ proof, ControlBlock, List.empty) } } } @@ -60,13 +99,13 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() - val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) + val filenameAdderPass = new l.AddFilenameRewriter(None) def parseUMCModel(file: java.io.File) : l.Module = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString filenameAdderPass.setFilename(filePath) filenameAdderPass.visit( parserObj.parseUMCModel(filePath, text), - uclid.lang.Scope.empty).get + l.Scope.empty).get } -} \ No newline at end of file +} diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 858c3ac0d..c425bddfb 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -159,7 +159,7 @@ object Operator { def oldInstance(c : OperatorApplication) = OperatorApplication(OldOperator(), List(c)) def history(c : Identifier, e : Expr) = OperatorApplication(HistoryOperator(), List(c, e)) } -sealed trait Operator extends ASTNode { +trait Operator extends ASTNode { def fixity : Int def isPolymorphic = false def isTemporal = false @@ -449,15 +449,14 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } -case class CountingOp(vs: List[(Identifier, Type)]) extends QuantifiedBooleanOperator { - override def variables = vs +/** CountingOp is used in the model counting extension. */ +case class CountingOp(vs: List[(Identifier, Type)]) extends Operator { override def toString() = "#[" + Utils.join(vs.map(_.toString()), ", ") + "]" override def fixity = Operator.PREFIX override val hashId = 1402 override val md5hashCode = computeMD5Hash(vs) } - // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { override def fixity = Operator.INFIX diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 5d22b0da0..988a5e3a9 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -373,13 +373,11 @@ class UclidParser extends UclidTokenParsers with PackratParsers { } | ConstArray | KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | - "#[" ~> IdTypeList ~ ( "]" ~ ":" ~ "(" ~> Expr <~ ")" ) ^^ { - case ids ~ expr => { - OperatorApplication(lang.CountingOp(ids), List(expr)) - } - } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | + ("#[" ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { + case vars ~ e => OperatorApplication(CountingOp(vars), List(e)) + } | Id } @@ -817,4 +815,4 @@ object UclidParser { parserObj.parseModel(filename, text) } } - \ No newline at end of file + diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 7186ad025..d145a6c70 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[n]:(f(n)) == #[n]:(g(n)) + #[n]:(h(n)); + assert or: #[(n:integer) :: f(n)] == + #[(n:integer) :: g(n)] + #[(n:integer) :: h(n)]; } } From 6a3dbdb16a131adcbd02319d8daaa0298e869ce5 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Fri, 10 Apr 2020 01:34:44 +0530 Subject: [PATCH 010/119] New syntax for counting op + rewriting infra setup. --- src/main/scala/uclid/Hashable.scala | 44 ++++-- src/main/scala/uclid/UclidMain.scala | 2 +- .../extensions/modelcounts/UMCMain.scala | 8 +- .../extensions/modelcounts/UMCParser.scala | 20 ++- .../extensions/modelcounts/UMCRewriter.scala | 141 ++++++++++++++++++ src/main/scala/uclid/lang/UclidLanguage.scala | 13 +- src/main/scala/uclid/lang/UclidParser.scala | 4 +- test/modelcounter/hello.ucl | 4 +- 8 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala diff --git a/src/main/scala/uclid/Hashable.scala b/src/main/scala/uclid/Hashable.scala index 908e13c39..f527fe364 100644 --- a/src/main/scala/uclid/Hashable.scala +++ b/src/main/scala/uclid/Hashable.scala @@ -22,19 +22,43 @@ trait Hashable { md5.reset() md5.update(intToBytes(hashBaseId)) md5.update(intToBytes(hashId)) + md5.update(intToBytes(args.size)) // Recursively compute the md5 hash def computeMD5HashR(a : Any) : Unit = { a match { - case None => computeMD5HashR(0) - case Some(opt) => computeMD5HashR(opt) - case integer : Int => md5.update(intToBytes(integer)) - case str : String => md5.update(str.getBytes("UTF-8")) - case bigint : BigInt => md5.update(bigint.toByteArray) - case bool : Boolean => md5.update(if (bool) intToBytes(1) else intToBytes(0)) - case tuple2 : (Any, Any) => { computeMD5HashR(tuple2._1); computeMD5HashR(tuple2._2); } - case tuple3 : (Any, Any, Any) => { computeMD5HashR(tuple3._1); computeMD5HashR(tuple3._2); computeMD5HashR(tuple3._3) } - case lista : List[Any] => lista.foreach(e => computeMD5HashR(e)) - case hash : Hashable => md5.update(hash.md5hashCode) + case None => + md5.update(intToBytes(111)) + computeMD5HashR(0) + case Some(opt) => + md5.update(intToBytes(112)) + computeMD5HashR(opt) + case integer : Int => + md5.update(intToBytes(113)) + md5.update(intToBytes(integer)) + case str : String => + md5.update(intToBytes(114)) + md5.update(str.getBytes("UTF-8")) + case bigint : BigInt => + md5.update(intToBytes(115)) + md5.update(bigint.toByteArray) + case bool : Boolean => + md5.update(intToBytes(116)) + md5.update(if (bool) intToBytes(1) else intToBytes(0)) + case tuple2 : (Any, Any) => + md5.update(intToBytes(117)) + computeMD5HashR(tuple2._1) + computeMD5HashR(tuple2._2) + case tuple3 : (Any, Any, Any) => + md5.update(intToBytes(118)) + computeMD5HashR(tuple3._1) + computeMD5HashR(tuple3._2) + computeMD5HashR(tuple3._3) + case lista : List[Any] => + md5.update(intToBytes(119)) + md5.update(intToBytes(lista.size)) + lista.foreach(e => computeMD5HashR(e)) + case hash : Hashable => + md5.update(hash.md5hashCode) case _ => { UclidMain.println("Can't convert: " + a.getClass().toString()) throw new Utils.RuntimeError("Should not get here; Missing case!" + a.getClass().toString()) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index b6e7a9099..65fd275fc 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -146,7 +146,7 @@ object UclidMain { return } if (config.modelCounter) { - config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f, config)) + config.files.foreach(f => extensions.modelcounts.UMCMain.checkModel(f, config)) return } val mainModuleName = Identifier(config.mainModuleName) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index ea3609b31..7be5237ba 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -9,7 +9,6 @@ * modification, are permitted provided that the following conditions are * met: * 1. Redistributions of source code must retain the above copyright notice, - * * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the @@ -36,14 +35,17 @@ * Main file for the UCLID model counter. * */ -package uclid.lang.modelcounts +package uclid.extensions.modelcounts import uclid.UclidMain import uclid.{lang => l} + object UMCMain { def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) - println("Managed to parse module: " + module.id.toString()) + println("Parsed module: " + module.id.toString()) + println(module.toString()) + new UMCRewriter(module).process() } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 18022fabd..52ddc30c0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -37,7 +37,7 @@ * */ -package uclid.lang.modelcounts +package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.lang.Identifier @@ -70,20 +70,26 @@ class UMCParser extends l.UclidParser { lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertDecl: PackratParser[l.SpecDecl] = positioned { + lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.SpecDecl(id, e, List.empty) + case id ~ e => l.AssertStmt(e, Some(id)) } } - lazy val ProofStmt: PackratParser[l.Decl] = - positioned ( AssertDecl ); - lazy val ProofScript: PackratParser[List[l.Decl]] = { + lazy val ProofStmt: PackratParser[l.AssertStmt] = + positioned ( AssertStmt ); + lazy val ProofScript: PackratParser[List[l.AssertStmt]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - l.Module(id, decls ++ proof, ControlBlock, List.empty) + val proc = l.ProcedureDecl( + l.Identifier("countingProof"), // procedure name + l.ProcedureSig(List.empty, List.empty), // signature + l.BlockStmt(List.empty, proof), // body + List.empty, List.empty, Set.empty, // requires, ensures, modifies + l.ProcedureAnnotations(Set.empty)) // no annotations. + l.Module(id, decls ++ List(proc) , ControlBlock, List.empty) } } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala new file mode 100644 index 000000000..2a5037d47 --- /dev/null +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -0,0 +1,141 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Rewriter for the UCLID5 model counter. + * + */ +package uclid.extensions.modelcounts + +import uclid.{lang => l} +import uclid.Memo + +import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} + +class UMCRewriter(module : l.Module) { + val proofProcedure = module.procedures(0) + + /** Identify counting ops in a sequence of expressions. + * + * Note the recursion is to identifyCountOps which is a Memo. + */ + def _identifyCountOps(es : Seq[l.Expr]) : Set[l.OperatorApplication] = { + es.foldLeft(Set.empty[l.OperatorApplication]) { + (acc, e) => acc ++ identifyCountOps(e) + } + } + /** Identify counting ops in an expression. + * + * Note recursion is to identifyCountsOp which is a memo. + */ + def _identifyCountOps(e : l.Expr) : Set[l.OperatorApplication] = { + e match { + case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => + Set.empty + case l.ConstArray(e, typ) => + identifyCountOps(e) + case l.Tuple(es) => + _identifyCountOps(es) + case opapp : l.OperatorApplication => + val init : Set[l.OperatorApplication] = opapp.op match { + case l.CountingOp(_, _) => Set(opapp) + case _ => Set.empty + } + init ++ _identifyCountOps(opapp.operands) + case l.FuncApplication(e, args) => + identifyCountOps(e) ++ _identifyCountOps(args) + case l.Lambda(ids, e) => + identifyCountOps(e) + } + } + + /** + * Memoizing wrapper for finding all counting operators. + */ + val identifyCountOps = new Memo[l.Expr, Set[l.OperatorApplication]](_identifyCountOps _) + + /** Finding all the counting operators in a list of assert statements. */ + def identifyCountOps(proofBlk: List[l.AssertStmt]) : Set[l.OperatorApplication] = { + proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { + (acc, st) => acc ++ identifyCountOps(st.e) + } + } + + /** Identifiers that are already declared in the module. */ + val existingIds = module.decls.map(d => d.declNames).flatten.toSet + /** Identifiers that are declared + newly generated names. */ + val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds + /** Counters that track (roughly) the number of generated identifiers with each prefix. */ + val counters = MutableMap.empty[String, Int] + /** Generate a new id. */ + def generateId(prefix: String) : l.Identifier = { + var cnt = counters.get(prefix) match { + case Some(n) => n + 1 + case None => 1 + } + def getName(n : Int) = l.Identifier(prefix + "_" + n.toString()) + var name = getName(cnt) + while (usedIds.contains(name)) { + cnt += 1 + name = getName(cnt) + } + usedIds += name + counters.put(prefix, cnt) + name + } + + /** Generate UF decls for the identified counting operators. + * + */ + def generateUFDecls(ops : Set[l.OperatorApplication]) : (Map[l.OperatorApplication, l.FunctionDecl]) = { + ops.map { + opapp => { + assert (opapp.op.isInstanceOf[l.CountingOp]) + val op = opapp.op.asInstanceOf[l.CountingOp] + val ufId = generateId("count") + val sig = l.FunctionSig(op.ys, l.IntegerType()) + val uf = l.FunctionDecl(ufId, sig) + opapp -> uf + } + }.toMap + } + + def process() : l.Module = { + val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) + val countingOps = identifyCountOps(proofProcBody) + val ufDecls = generateUFDecls(countingOps) + println(ufDecls.toString()) + module + } +} \ No newline at end of file diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index c425bddfb..8701b9646 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -450,12 +450,15 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val md5hashCode = computeMD5Hash(vs, patterns) } /** CountingOp is used in the model counting extension. */ -case class CountingOp(vs: List[(Identifier, Type)]) extends Operator { - override def toString() = "#[" + - Utils.join(vs.map(_.toString()), ", ") + "]" +case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)]) extends Operator { + override def toString() = { + val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + "#[(" + s1 + ") for (" + s2 + ") :: " + } override def fixity = Operator.PREFIX override val hashId = 1402 - override val md5hashCode = computeMD5Hash(vs) + override val md5hashCode = computeMD5Hash(xs, ys) } // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { @@ -753,6 +756,8 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString + case CountingOp(_, _) => + op.toString() + operands(0).toString() + "]" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 988a5e3a9..1b27d7e19 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -375,8 +375,8 @@ class UclidParser extends UclidTokenParsers with PackratParsers { KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | - ("#[" ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { - case vars ~ e => OperatorApplication(CountingOp(vars), List(e)) + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { + case xs ~ ys ~ e => OperatorApplication(CountingOp(xs, ys), List(e)) } | Id } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index d145a6c70..e4e63fd52 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,8 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[(n:integer) :: f(n)] == - #[(n:integer) :: g(n)] + #[(n:integer) :: h(n)]; + assert or: #[(n:integer) for () :: f(n)] == + #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; } } From f72dec41bfae076960761a35c284b796be53f87e Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 00:21:57 +0530 Subject: [PATCH 011/119] Adding a parameter list to assert statements --- src/main/scala/uclid/SymbolicSimulator.scala | 4 +- .../extensions/modelcounts/UMCMain.scala | 3 +- .../extensions/modelcounts/UMCParser.scala | 2 +- .../extensions/modelcounts/UMCRewriter.scala | 50 ++++++++++++++++--- src/main/scala/uclid/lang/ASTVistors.scala | 4 +- .../uclid/lang/ModularProductProgram.scala | 13 +++-- .../scala/uclid/lang/ModuleFlattener.scala | 4 +- src/main/scala/uclid/lang/UclidLanguage.scala | 16 ++++-- test/modelcounter/hello.ucl | 4 +- 9 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/main/scala/uclid/SymbolicSimulator.scala b/src/main/scala/uclid/SymbolicSimulator.scala index c09d0b53a..e129a7b21 100644 --- a/src/main/scala/uclid/SymbolicSimulator.scala +++ b/src/main/scala/uclid/SymbolicSimulator.scala @@ -1741,7 +1741,7 @@ class SymbolicSimulator (module : Module) { frameLog.debug("symbolTable: %s".format(symbolTable.toString())) s match { case SkipStmt() => return symbolTable - case AssertStmt(e, id) => + case AssertStmt(e, id, params) => val frameTableP = frameTable.clone() frameTableP += symbolTable val simTable = ArrayBuffer(frameTableP) @@ -1829,7 +1829,7 @@ class SymbolicSimulator (module : Module) { def writeSet(stmt: Statement) : Set[Identifier] = stmt match { case SkipStmt() => Set.empty - case AssertStmt(e, id) => Set.empty + case AssertStmt(e, id, params) => Set.empty case AssumeStmt(e, id) => Set.empty case HavocStmt(h) => h match { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 7be5237ba..5f29be439 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -46,6 +46,7 @@ object UMCMain { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) println(module.toString()) - new UMCRewriter(module).process() + val moduleP = new UMCRewriter(module).process() + println(moduleP.toString()) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 52ddc30c0..fe8f879af 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -72,7 +72,7 @@ class UMCParser extends l.UclidParser { lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.AssertStmt(e, Some(id)) + case id ~ e => l.AssertStmt(e, Some(id), List.empty) } } lazy val ProofStmt: PackratParser[l.AssertStmt] = diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2a5037d47..7a16cdb30 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -45,12 +45,18 @@ import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} class UMCRewriter(module : l.Module) { val proofProcedure = module.procedures(0) + + /* We will be using this set in a number of places. */ + type CountingOpSet = Set[l.OperatorApplication] + /* A map from counting ops to the UFs that represent them. */ + type UFMap = Map[l.OperatorApplication, l.FunctionDecl] + /** Identify counting ops in a sequence of expressions. * * Note the recursion is to identifyCountOps which is a Memo. */ - def _identifyCountOps(es : Seq[l.Expr]) : Set[l.OperatorApplication] = { + def _identifyCountOps(es : Seq[l.Expr]) : CountingOpSet = { es.foldLeft(Set.empty[l.OperatorApplication]) { (acc, e) => acc ++ identifyCountOps(e) } @@ -59,7 +65,7 @@ class UMCRewriter(module : l.Module) { * * Note recursion is to identifyCountsOp which is a memo. */ - def _identifyCountOps(e : l.Expr) : Set[l.OperatorApplication] = { + def _identifyCountOps(e : l.Expr) : CountingOpSet = { e match { case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => Set.empty @@ -68,7 +74,7 @@ class UMCRewriter(module : l.Module) { case l.Tuple(es) => _identifyCountOps(es) case opapp : l.OperatorApplication => - val init : Set[l.OperatorApplication] = opapp.op match { + val init : CountingOpSet = opapp.op match { case l.CountingOp(_, _) => Set(opapp) case _ => Set.empty } @@ -83,10 +89,10 @@ class UMCRewriter(module : l.Module) { /** * Memoizing wrapper for finding all counting operators. */ - val identifyCountOps = new Memo[l.Expr, Set[l.OperatorApplication]](_identifyCountOps _) + val identifyCountOps = new Memo[l.Expr, CountingOpSet](_identifyCountOps _) /** Finding all the counting operators in a list of assert statements. */ - def identifyCountOps(proofBlk: List[l.AssertStmt]) : Set[l.OperatorApplication] = { + def identifyCountOps(proofBlk: List[l.AssertStmt]) : CountingOpSet = { proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { (acc, st) => acc ++ identifyCountOps(st.e) } @@ -118,7 +124,7 @@ class UMCRewriter(module : l.Module) { /** Generate UF decls for the identified counting operators. * */ - def generateUFDecls(ops : Set[l.OperatorApplication]) : (Map[l.OperatorApplication, l.FunctionDecl]) = { + def generateCountingOpToUFMap(ops : CountingOpSet) : (UFMap) = { ops.map { opapp => { assert (opapp.op.isInstanceOf[l.CountingOp]) @@ -131,11 +137,39 @@ class UMCRewriter(module : l.Module) { }.toMap } + /** + * Create the list of UF + Axiom declarations. + */ + def generateUFDecls(ufMap : UFMap) : List[l.Decl] = { + def geqZero(e : l.Expr) : l.Expr = { + l.OperatorApplication(l.IntGEOp(), List(e, l.IntLit(0))) + } + def quantify(ufDecl : l.FunctionDecl, e : l.Expr) : l.Expr = { + if (ufDecl.sig.args.size > 0) { + l.OperatorApplication(l.ForallOp(ufDecl.sig.args, List.empty), List(e)) + } else { + e + } + } + ufMap.map { + p => { + val ufDecl = p._2 + val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) + val axExpr = quantify(ufDecl, innerExpr) + val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) + List(ufDecl, axiomDecl) + } + }.flatten.toList + } + def process() : l.Module = { val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) val countingOps = identifyCountOps(proofProcBody) - val ufDecls = generateUFDecls(countingOps) + val ufMap = generateCountingOpToUFMap(countingOps) + val ufDecls = generateUFDecls(ufMap) + val moduleP = l.Module(module.id, module.decls ++ ufDecls, module.cmds, module.notes) + println(ufMap.toString()) println(ufDecls.toString()) - module + moduleP } } \ No newline at end of file diff --git a/src/main/scala/uclid/lang/ASTVistors.scala b/src/main/scala/uclid/lang/ASTVistors.scala index e53236903..96916db8b 100644 --- a/src/main/scala/uclid/lang/ASTVistors.scala +++ b/src/main/scala/uclid/lang/ASTVistors.scala @@ -743,6 +743,7 @@ class ASTAnalyzer[T] (_passName : String, _pass: ReadOnlyPass[T]) extends ASTAna case None => result case Some(id) => visitIdentifier(id, result, context) } + result = st.params.foldLeft(result)((acc, e) => visitExpr(e, acc, context)) val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment result = visitExpr(st.e, result, context.withEnvironment(envP)) result = pass.applyOnAssert(TraversalDirection.Up, st, result, context) @@ -1609,9 +1610,10 @@ class ASTRewriter (_passName : String, _pass: RewritePass, setFilename : Boolean def visitAssertStatement(st : AssertStmt, context : Scope) : Option[Statement] = { val idP = st.id.flatMap(id => visitIdentifier(id, context)) + val paramsP = st.params.map(p => visitExpr(p, context)).flatten val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment val stP = visitExpr(st.e, context.withEnvironment(envP)).flatMap((e) => { - pass.rewriteAssert(AssertStmt(e, idP), context) + pass.rewriteAssert(AssertStmt(e, idP, paramsP), context) }) return ASTNode.introducePos(setPosition, setFilename, stP, st.position) } diff --git a/src/main/scala/uclid/lang/ModularProductProgram.scala b/src/main/scala/uclid/lang/ModularProductProgram.scala index bf0bb6f07..ad149d3cb 100644 --- a/src/main/scala/uclid/lang/ModularProductProgram.scala +++ b/src/main/scala/uclid/lang/ModularProductProgram.scala @@ -524,7 +524,7 @@ class ModularProductProgramPass extends RewritePass { } } - case AssertStmt(expr, id) => + case AssertStmt(expr, id, params) => val activationVariableArray = helperObj.mapOfActivationVariables(currentScope) val emptyVarsList: List[BlockVarsDecl] = List() expr match { @@ -546,7 +546,8 @@ class ModularProductProgramPass extends RewritePass { andCondition = Operator.and(andCondition, checkActVarCondition) } val renamedExpression = getRenamedExpr(expr, context, k) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, k)) + val newAssertStatement = AssertStmt(renamedExpression, id, renamedParams) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -560,7 +561,8 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, i)) + val newAssertStatement = AssertStmt(renamedExpression, id, params) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -576,7 +578,8 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, i)) + val newAssertStatement = AssertStmt(renamedExpression, id, params) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -1094,7 +1097,7 @@ class ModularProductProgramPass extends RewritePass { newstmts += oldstmts.head } - case AssertStmt(expr, _) => + case AssertStmt(expr, id, params) => expr match { case OperatorApplication(op, operands) => val hasHyperSelect = isHyperSelectPresent(op, operands) diff --git a/src/main/scala/uclid/lang/ModuleFlattener.scala b/src/main/scala/uclid/lang/ModuleFlattener.scala index de1c4749d..4e920957e 100644 --- a/src/main/scala/uclid/lang/ModuleFlattener.scala +++ b/src/main/scala/uclid/lang/ModuleFlattener.scala @@ -550,7 +550,7 @@ class ModuleInstantiatorPass(module : Module, inst : InstanceDecl, targetModule val preconditionAsserts : List[Statement] = proc.requires.map { (req) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(req, context), context) - val node = AssertStmt(exprP, Some(Identifier("precondition"))) + val node = AssertStmt(exprP, Some(Identifier("precondition")), List.empty) ASTNode.introducePos(true, true, node, req.position) } } @@ -559,7 +559,7 @@ class ModuleInstantiatorPass(module : Module, inst : InstanceDecl, targetModule proc.ensures.map { (ens) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(ens, context), context) - val node = AssertStmt(exprP, Some(Identifier("postcondition"))) + val node = AssertStmt(exprP, Some(Identifier("postcondition")), List.empty) ASTNode.introducePos(true, true, node, ens.position) } } diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 8701b9646..3a7445ffa 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -460,6 +460,7 @@ case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)] override val hashId = 1402 override val md5hashCode = computeMD5Hash(xs, ys) } + // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { override def fixity = Operator.INFIX @@ -1148,7 +1149,7 @@ case class HavocableInstanceId(opapp : OperatorApplication) extends HavocableEnt } /** Statements **/ -sealed abstract class Statement extends ASTNode { +abstract class Statement extends ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def hasStmtBlock = false val isLoop = false @@ -1164,8 +1165,17 @@ case class SkipStmt() extends Statement { override val hashId = 3000 override val md5hashCode = computeMD5Hash } -case class AssertStmt(e: Expr, id : Option[Identifier]) extends Statement { - override def toLines = List("assert " + e + "; // " + position.toString) +case class AssertStmt(e: Expr, id : Option[Identifier], params: List[Expr]) extends Statement { + override def toLines = { + val paramStr = if (params.size > 0) { + " [" + Utils.join(params.map(_.toString()), ", ") + "] " + } else { "" } + val prefix = id match { + case Some(n) => "assert " + n.toString() + paramStr + ": " + case None => "assert " + paramStr + } + List(prefix + e.toString() + "; // " + position.toString) + } override val hasCall = false override val hasInternalCall = false override val hashId = 3001 diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index e4e63fd52..fc0f06f82 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,8 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[(n:integer) for () :: f(n)] == - #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; + assert disjoint: #[(n:integer) for () :: f(n)] == + #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; } } From ba1bd5c17b6f06290f9e946dbe0104ea3f3f3505 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 00:33:31 +0530 Subject: [PATCH 012/119] More fixes for assertion parameters. --- src/main/scala/uclid/lang/ModuleTypeChecker.scala | 2 +- .../scala/uclid/lang/PrimedAssignmentChecker.scala | 2 +- src/main/scala/uclid/lang/ProcedureInliner.scala | 4 ++-- src/main/scala/uclid/lang/StatementScheduler.scala | 12 ++++++------ src/main/scala/uclid/lang/UclidParser.scala | 2 +- src/main/scala/uclid/lang/WhileLoopRewriter.scala | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/scala/uclid/lang/ModuleTypeChecker.scala b/src/main/scala/uclid/lang/ModuleTypeChecker.scala index 141e65cff..5917d55fc 100644 --- a/src/main/scala/uclid/lang/ModuleTypeChecker.scala +++ b/src/main/scala/uclid/lang/ModuleTypeChecker.scala @@ -50,7 +50,7 @@ class ModuleTypeCheckerPass extends ReadOnlyPass[Set[ModuleError]] in } else { st match { - case AssertStmt(e, _) => + case AssertStmt(e, id, params) => val eType = exprTypeChecker.typeOf(e, context) if (!eType.isBool) { in + ModuleError("Assertion expression must be of Boolean or Temporal type", st.position) diff --git a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala index f8864503d..1244a5234 100644 --- a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala +++ b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala @@ -103,7 +103,7 @@ class PrimedAssignmentCheckerPass extends ReadOnlyPass[Set[ModuleError]] case IfElseStmt(_, _, _) | ForStmt(_, _, _, _) | WhileStmt(_, _, _) | CaseStmt(_) | SkipStmt() | - AssertStmt(_, _) | AssumeStmt(_, _) | + AssertStmt(_, _, _) | AssumeStmt(_, _) | HavocStmt(_) | BlockStmt(_, _) => in case ModuleCallStmt(_) => diff --git a/src/main/scala/uclid/lang/ProcedureInliner.scala b/src/main/scala/uclid/lang/ProcedureInliner.scala index b3ae57d3c..183ba2203 100644 --- a/src/main/scala/uclid/lang/ProcedureInliner.scala +++ b/src/main/scala/uclid/lang/ProcedureInliner.scala @@ -185,7 +185,7 @@ trait NewProcedureInlinerPass extends RewritePass { val preconditionAsserts : List[Statement] = proc.requires.map { (req) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(req, context), context) - val node = AssertStmt(exprP, Some(Identifier("precondition"))) + val node = AssertStmt(exprP, Some(Identifier("precondition")), List.empty) ASTNode.introducePos(true, true, node, req.position) } } @@ -194,7 +194,7 @@ trait NewProcedureInlinerPass extends RewritePass { proc.ensures.map { (ens) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(ens, context), context) - val node = AssertStmt(exprP, Some(Identifier("postcondition"))) + val node = AssertStmt(exprP, Some(Identifier("postcondition")), List.empty) ASTNode.introducePos(true, true, node, ens.position) } } diff --git a/src/main/scala/uclid/lang/StatementScheduler.scala b/src/main/scala/uclid/lang/StatementScheduler.scala index 324a44fb0..86d505bfd 100644 --- a/src/main/scala/uclid/lang/StatementScheduler.scala +++ b/src/main/scala/uclid/lang/StatementScheduler.scala @@ -48,8 +48,8 @@ object StatementScheduler { def writeSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(_, _) => Set.empty - case AssumeStmt(_, _) => Set.empty + case AssertStmt(e, _, _) => Set.empty + case AssumeStmt(e, _) => Set.empty case HavocStmt(h) => h match { case HavocableId(id) => Set(id) @@ -111,8 +111,8 @@ object StatementScheduler { def writeSetIds(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(_, _) => Set.empty - case AssumeStmt(_, _) => Set.empty + case AssertStmt(e, _, _) => Set.empty + case AssumeStmt(e, _) => Set.empty case HavocStmt(h) => h match { case HavocableId(id) => Set(id) @@ -182,7 +182,7 @@ object StatementScheduler { def readSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(e, _) => readSet(e) + case AssertStmt(e, _, _) => readSet(e) case AssumeStmt(e, _) => readSet(e) case HavocStmt(_) => Set.empty case AssignStmt(_, rhss) => readSets(rhss) @@ -236,7 +236,7 @@ object StatementScheduler { def primeReadSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(e, _) => primeReadSet(e) + case AssertStmt(e, _, _) => primeReadSet(e) case AssumeStmt(e, _) => primeReadSet(e) case HavocStmt(_) => Set.empty case AssignStmt(_, rhss) => primeReadSets(rhss) diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 1b27d7e19..7cb7b1304 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -462,7 +462,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Statement: PackratParser[Statement] = positioned { KwSkip <~ ";" ^^ { case _ => SkipStmt() } | - KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None) } | + KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None, List.empty) } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ diff --git a/src/main/scala/uclid/lang/WhileLoopRewriter.scala b/src/main/scala/uclid/lang/WhileLoopRewriter.scala index e492e686b..6c81b5fc5 100644 --- a/src/main/scala/uclid/lang/WhileLoopRewriter.scala +++ b/src/main/scala/uclid/lang/WhileLoopRewriter.scala @@ -47,7 +47,7 @@ class WhileLoopRewriterPass extends RewritePass { val invs = whileSt.invariants val initialAsserts = invs.map{ inv => { - ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (entry)"))), inv.position) + ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (entry)")), List.empty), inv.position) } } val varsToHavoc = StatementScheduler.writeSetIds(whileSt.body, context).toList @@ -59,7 +59,7 @@ class WhileLoopRewriterPass extends RewritePass { val assumeStmts = AssumeStmt(cond, None) :: invs.map(inv => AssumeStmt(inv, None)) val assertStmts = invs.map{ inv => { - ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (iteration)"))), inv.position) + ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (iteration)")), List.empty), inv.position) } } val finishAssump = AssumeStmt(Operator.not(cond), None) From 09b726f74b130cf1af5f1b847eb401c82879320a Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 01:32:31 +0530 Subject: [PATCH 013/119] Almost done with the initial working example. --- .../extensions/modelcounts/UMCRewriter.scala | 94 ++++++++++++++++++- src/main/scala/uclid/lang/UclidLanguage.scala | 4 +- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 7a16cdb30..50009a9ef 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -120,7 +120,6 @@ class UMCRewriter(module : l.Module) { counters.put(prefix, cnt) name } - /** Generate UF decls for the identified counting operators. * */ @@ -161,13 +160,104 @@ class UMCRewriter(module : l.Module) { } }.flatten.toList } + + // Helper functions to more easily construct expressions. + def _forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + + def _and(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) + } + + def _or(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) + } + + def _iff(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IffOp(), List(e1, e2)) + } + + def _not(e : l.Expr) = { + l.OperatorApplication(l.NegationOp(), List(e)) + } + + def _eq(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.EqualityOp(), List(e1, e2)) + } + + def _plus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.AddOp(), List(e1, e2)) + } + + def extractCountingArgs(e : l.Expr) = { + assert (e.isInstanceOf[l.OperatorApplication]) + val opapp = e.asInstanceOf[l.OperatorApplication] + opapp.op match { + case l.CountingOp(l1, l2) => l1 ++ l2 + case _ => throw new AssertionError("Unexpected operator") + } + } + + def extractFunction(e : l.Expr) = { + assert (e.isInstanceOf[l.OperatorApplication]) + val opapp = e.asInstanceOf[l.OperatorApplication] + opapp.operands(0) + } + + def _apply(uf : l.FunctionDecl) = { + l.FuncApplication(uf.id, uf.sig.args.map(_._1)) + } + def rewriteDisjoint(ufMap : UFMap, st : l.AssertStmt) : List[l.Statement] = { + val e = st.e + e match { + case l.OperatorApplication(l.EqualityOp(), List(e1, l.OperatorApplication(l.AddOp(), List(e2, e3)))) => + val o1 = e1.asInstanceOf[l.OperatorApplication] + val o2 = e2.asInstanceOf[l.OperatorApplication] + val o3 = e3.asInstanceOf[l.OperatorApplication] + val args = extractCountingArgs(e1) + val f1 = extractFunction(e1) + val f2 = extractFunction(e2) + val f3 = extractFunction(e3) + val assertExpr = _and(_forall(args, _iff(f1, _or(f2, f3))), + _forall(args, _not(_and(f2, f3)))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(o1)) + val ufn2 = _apply(ufMap(o2)) + val ufn3 = _apply(ufMap(o3)) + val assumeExpr = _forall(args, _eq(ufn1, _plus(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + case _ => + throw new AssertionError("Unexpected expression in rewriteDisjoint: " + e.toString()) + } + } + def rewriteAssert(ufmap : UFMap, st : l.AssertStmt) : List[l.Statement] = { + st.id match { + case Some(l.Identifier("disjoint")) => + rewriteDisjoint(ufmap, st) + case _ => + throw new AssertionError("Unknown rule: " + st.id.toString()) + } + } + def process() : l.Module = { + val proofProc = module.procedures(0) val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) val countingOps = identifyCountOps(proofProcBody) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val moduleP = l.Module(module.id, module.decls ++ ufDecls, module.cmds, module.notes) + val newProofStmts = proofProcBody.map(st => rewriteAssert(ufMap, st)).flatten + val newProofProc = l.ProcedureDecl( + l.Identifier("newCountingProof"), proofProc.sig, + l.BlockStmt(List.empty, newProofStmts), + List.empty, List.empty, Set.empty, proofProc.annotations) + val prevDecls = module.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val moduleP = l.Module(module.id, + prevDecls ++ ufDecls ++ List(newProofProc), + module.cmds, module.notes) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 3a7445ffa..5cae24136 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -1703,10 +1703,10 @@ case class AxiomDecl(id : Option[Identifier], expr: Expr, params: List[ExprDecor override val hashId = 3918 override val md5hashCode = computeMD5Hash(id, expr, params) override def toString = { - id match { + (id match { case Some(id) => "axiom " + id.toString + " : " + expr.toString() case None => "axiom " + expr.toString - } + }) + "; // " + pos.toString() } override def declNames = id match { case Some(i) => List(i) From ee3ba8865a8e7bfec2f41cf0b5897bd08188ed3c Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sun, 12 Apr 2020 02:05:38 +0530 Subject: [PATCH 014/119] Refactoring the UMC ASTs. This should make rewriting much cleaner. --- src/main/scala/uclid/UclidMain.scala | 13 +- .../extensions/modelcounts/UMCLanguage.scala | 117 +++++++++++ .../extensions/modelcounts/UMCMain.scala | 15 ++ .../extensions/modelcounts/UMCParser.scala | 68 +++---- .../extensions/modelcounts/UMCRewriter.scala | 191 ++++++------------ src/main/scala/uclid/lang/UclidLanguage.scala | 19 +- src/main/scala/uclid/lang/UclidParser.scala | 3 - test/modelcounter/hello.ucl | 8 +- 8 files changed, 235 insertions(+), 199 deletions(-) create mode 100644 src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 65fd275fc..cb249bbca 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -248,7 +248,6 @@ object UclidMain { def compile(srcFiles : Seq[java.io.File], mainModuleName : Identifier, test : Boolean = false): List[Module] = { type NameCountMap = Map[Identifier, Int] var nameCnt : NameCountMap = Map().withDefaultValue(0) - val passManager = createCompilePassManager(test, mainModuleName) val filenameAdderPass = new AddFilenameRewriter(None) // Helper function to parse a single file. @@ -261,7 +260,17 @@ object UclidMain { val parsedModules = srcFiles.foldLeft(List.empty[Module]) { (acc, srcFile) => acc ++ parseFile(srcFile.getPath()) } - + compileModules(parsedModules, mainModuleName, test) + } + + /** Compile a list of modules (do everything pre-module-instantiation. */ + def compileModules( + parsedModules : List[Module], + mainModuleName : Identifier, + test : Boolean) : List[Module] = + { + // create a pass manager. + val passManager = createCompilePassManager(test, mainModuleName) // now process each module val init = (List.empty[Module], Scope.empty) // NOTE: The foldLeft/:: combination here reverses the order of modules. diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala new file mode 100644 index 000000000..32a5cac95 --- /dev/null +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -0,0 +1,117 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Main file for the UCLID model counter. + * + */ +package uclid.extensions.modelcounts + +import uclid.UclidMain +import uclid.{lang => l} +import uclid.Utils + + +/** CountingOp is a new operator we introduce for the UMC extension. */ +case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, l.Type)], e : l.Expr) extends l.Expr { + override def toString() = { + val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + val s2 = if (ys.size > 0) { + val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + "#[(" + s1 + ") for (" + s2 + ")] :: " + } else { + "#[(" + s1 + ")] :: " + } + s2 + "(" + e.toString() + ")" + } + override val hashId = 1402 + override val md5hashCode = computeMD5Hash(xs, ys) +} + +/** This is the base class for all the "statements" in the proof. */ +abstract class Statement extends l.ASTNode { + override def toString = Utils.join(toLines, "\n") + "\n" + def toLines : List[String] + def expressions : Seq[CountingOp] +} + +case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + override val hashId = 130001 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def expressions = Seq(e1, e2, e3) +} + +case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { + override def toString = { + "module " + id.toString() + " {\n" + + Utils.join(decls.map(" " + _.toString()), "\n") + + "\n\n proof {\n" + + Utils.join(stmts.map(st => " " + st.toString()), "\n") + + "\n }\n}" + } + override val hashId = 131001 + override val md5hashCode = computeMD5Hash(id, decls, stmts) +} +/** Helpers to construct UCLID5 expressions. */ +object UMCExpressions { + // Helper functions to more easily construct expressions. + def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + + def and(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) + } + + def or(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) + } + + def iff(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IffOp(), List(e1, e2)) + } + + def not(e : l.Expr) = { + l.OperatorApplication(l.NegationOp(), List(e)) + } + + def eq(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.EqualityOp(), List(e1, e2)) + } + + def plus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.AddOp(), List(e1, e2)) + } +} diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 5f29be439..87661f2a6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -39,14 +39,29 @@ package uclid.extensions.modelcounts import uclid.UclidMain import uclid.{lang => l} +import uclid.Utils object UMCMain { + /** Executes regular UCLID5 on the processed module. */ + def runProcessedModel(module : l.Module) : Unit = { + val config = UclidMain.Config() + val mainModuleName = l.Identifier("main") + val modules = UclidMain.compileModules(List(module), mainModuleName, false) + val mainModule = UclidMain.instantiate(config, modules, mainModuleName, true) + mainModule match { + case Some(m) => UclidMain.execute(m, config) + case None => + throw new Utils.ParserError("Unable to find main module", None, None) + } + } + def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) println(module.toString()) val moduleP = new UMCRewriter(module).process() println(moduleP.toString()) + runProcessedModel(moduleP) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index fe8f879af..2e7f2888b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -40,63 +40,52 @@ package uclid.extensions.modelcounts import uclid.{lang => l} +import uclid.Utils import uclid.lang.Identifier import uclid.smt.IntLit - +import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" - lexical.reserved += (KwProof) - - lazy val ControlBlock : List[l.GenericProofCommand] = List( - l.GenericProofCommand( - l.Identifier("unroll"), - List.empty, List((l.IntLit(1), "1")), - Some(l.Identifier("v")), None), - l.GenericProofCommand( - l.Identifier("check"), - List.empty, List.empty, - None, None), - l.GenericProofCommand( - l.Identifier("print_results"), - List.empty, List.empty, - None, None), - l.GenericProofCommand( - l.Identifier("print_cex"), - List.empty, List.empty, - None, Some(l.Identifier("v"))) - ) + lazy val KwDisjoint = "disjoint" + + lexical.reserved += (KwProof, KwDisjoint) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { - KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.AssertStmt(e, Some(id), List.empty) + lazy val CountingExpr : PackratParser[CountingOp] = positioned { + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { + case xs ~ ys ~ e => CountingOp(xs, ys, e) + } | + ("#[" ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { + case xs ~ e => CountingOp(xs, List.empty, e) + } + } + + lazy val AssertStmt: PackratParser[Statement] = positioned { + KwAssert ~ KwDisjoint ~ ":" ~> + (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ + { + case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) } } - lazy val ProofStmt: PackratParser[l.AssertStmt] = + lazy val ProofStmt: PackratParser[Statement] = positioned ( AssertStmt ); - lazy val ProofScript: PackratParser[List[l.AssertStmt]] = { + lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } - lazy val UMCModule: PackratParser[l.Module] = positioned { + lazy val CntProof: PackratParser[CountingProof] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - val proc = l.ProcedureDecl( - l.Identifier("countingProof"), // procedure name - l.ProcedureSig(List.empty, List.empty), // signature - l.BlockStmt(List.empty, proof), // body - List.empty, List.empty, Set.empty, // requires, ensures, modifies - l.ProcedureAnnotations(Set.empty)) // no annotations. - l.Module(id, decls ++ List(proc) , ControlBlock, List.empty) + CountingProof(id, decls, proof) } } } - def parseUMCModel(filename : String, text: String): l.Module = { + def parseUMCModel(filename : String, text: String): CountingProof = { val tokens = new PackratReader(new lexical.Scanner(text)) - phrase(UMCModule)(tokens) match { + phrase(CntProof)(tokens) match { case Success(module, _) => module case NoSuccess(msg, next) => throw new uclid.Utils.SyntaxError(msg, Some(next.pos), Some(filename)) } @@ -106,12 +95,9 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() val filenameAdderPass = new l.AddFilenameRewriter(None) - def parseUMCModel(file: java.io.File) : l.Module = { + def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString - filenameAdderPass.setFilename(filePath) - filenameAdderPass.visit( - parserObj.parseUMCModel(filePath, text), - l.Scope.empty).get + parserObj.parseUMCModel(filePath, text) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 50009a9ef..8f1ef82c3 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -40,66 +40,25 @@ package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.Memo - +import uclid.extensions.modelcounts.{UMCExpressions => E} import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} -class UMCRewriter(module : l.Module) { - val proofProcedure = module.procedures(0) - +class UMCRewriter(cntProof : CountingProof) { /* We will be using this set in a number of places. */ - type CountingOpSet = Set[l.OperatorApplication] + type CountingOpSet = Set[CountingOp] /* A map from counting ops to the UFs that represent them. */ - type UFMap = Map[l.OperatorApplication, l.FunctionDecl] + type UFMap = Map[CountingOp, l.FunctionDecl] - /** Identify counting ops in a sequence of expressions. - * - * Note the recursion is to identifyCountOps which is a Memo. - */ - def _identifyCountOps(es : Seq[l.Expr]) : CountingOpSet = { - es.foldLeft(Set.empty[l.OperatorApplication]) { - (acc, e) => acc ++ identifyCountOps(e) - } - } - /** Identify counting ops in an expression. - * - * Note recursion is to identifyCountsOp which is a memo. - */ - def _identifyCountOps(e : l.Expr) : CountingOpSet = { - e match { - case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => - Set.empty - case l.ConstArray(e, typ) => - identifyCountOps(e) - case l.Tuple(es) => - _identifyCountOps(es) - case opapp : l.OperatorApplication => - val init : CountingOpSet = opapp.op match { - case l.CountingOp(_, _) => Set(opapp) - case _ => Set.empty - } - init ++ _identifyCountOps(opapp.operands) - case l.FuncApplication(e, args) => - identifyCountOps(e) ++ _identifyCountOps(args) - case l.Lambda(ids, e) => - identifyCountOps(e) - } - } - - /** - * Memoizing wrapper for finding all counting operators. - */ - val identifyCountOps = new Memo[l.Expr, CountingOpSet](_identifyCountOps _) - /** Finding all the counting operators in a list of assert statements. */ - def identifyCountOps(proofBlk: List[l.AssertStmt]) : CountingOpSet = { - proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { - (acc, st) => acc ++ identifyCountOps(st.e) + def identifyCountOps(proofBlk: List[Statement]) : CountingOpSet = { + proofBlk.foldLeft(Set.empty[CountingOp]) { + (acc, st) => acc ++ st.expressions.toSet } } /** Identifiers that are already declared in the module. */ - val existingIds = module.decls.map(d => d.declNames).flatten.toSet + val existingIds = cntProof.decls.map(d => d.declNames).flatten.toSet /** Identifiers that are declared + newly generated names. */ val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds /** Counters that track (roughly) the number of generated identifiers with each prefix. */ @@ -125,13 +84,11 @@ class UMCRewriter(module : l.Module) { */ def generateCountingOpToUFMap(ops : CountingOpSet) : (UFMap) = { ops.map { - opapp => { - assert (opapp.op.isInstanceOf[l.CountingOp]) - val op = opapp.op.asInstanceOf[l.CountingOp] + op => { val ufId = generateId("count") val sig = l.FunctionSig(op.ys, l.IntegerType()) val uf = l.FunctionDecl(ufId, sig) - opapp -> uf + op -> uf } }.toMap } @@ -161,103 +118,71 @@ class UMCRewriter(module : l.Module) { }.flatten.toList } - // Helper functions to more easily construct expressions. - def _forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - val op = l.ForallOp(vs, List.empty) - l.OperatorApplication(op, List(e)) - } - - def _and(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) - } - - def _or(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) - } - - def _iff(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.IffOp(), List(e1, e2)) - } - - def _not(e : l.Expr) = { - l.OperatorApplication(l.NegationOp(), List(e)) - } - - def _eq(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.EqualityOp(), List(e1, e2)) - } - - def _plus(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.AddOp(), List(e1, e2)) - } - - def extractCountingArgs(e : l.Expr) = { - assert (e.isInstanceOf[l.OperatorApplication]) - val opapp = e.asInstanceOf[l.OperatorApplication] - opapp.op match { - case l.CountingOp(l1, l2) => l1 ++ l2 - case _ => throw new AssertionError("Unexpected operator") - } - } - - def extractFunction(e : l.Expr) = { - assert (e.isInstanceOf[l.OperatorApplication]) - val opapp = e.asInstanceOf[l.OperatorApplication] - opapp.operands(0) + def extractCountingArgs(e : CountingOp) = { + e.xs ++ e.ys } def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } - def rewriteDisjoint(ufMap : UFMap, st : l.AssertStmt) : List[l.Statement] = { - val e = st.e - e match { - case l.OperatorApplication(l.EqualityOp(), List(e1, l.OperatorApplication(l.AddOp(), List(e2, e3)))) => - val o1 = e1.asInstanceOf[l.OperatorApplication] - val o2 = e2.asInstanceOf[l.OperatorApplication] - val o3 = e3.asInstanceOf[l.OperatorApplication] - val args = extractCountingArgs(e1) - val f1 = extractFunction(e1) - val f2 = extractFunction(e2) - val f3 = extractFunction(e3) - val assertExpr = _and(_forall(args, _iff(f1, _or(f2, f3))), - _forall(args, _not(_and(f2, f3)))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) - val ufn1 = _apply(ufMap(o1)) - val ufn2 = _apply(ufMap(o2)) - val ufn3 = _apply(ufMap(o3)) - val assumeExpr = _forall(args, _eq(ufn1, _plus(ufn2, ufn3))) - val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) - case _ => - throw new AssertionError("Unexpected expression in rewriteDisjoint: " + e.toString()) - } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + val o1 = st.e1 + val o2 = st.e2 + val o3 = st.e3 + val args = extractCountingArgs(o1) + val f1 = o1.e + val f2 = o2.e + val f3 = o3.e + val assertExpr = E.and(E.forall(args, E.iff(f1, E.or(f2, f3))), + E.forall(args, E.not(E.and(f2, f3)))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(o1)) + val ufn2 = _apply(ufMap(o2)) + val ufn3 = _apply(ufMap(o3)) + val assumeExpr = E.forall(args, E.eq(ufn1, E.plus(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) } - def rewriteAssert(ufmap : UFMap, st : l.AssertStmt) : List[l.Statement] = { - st.id match { - case Some(l.Identifier("disjoint")) => - rewriteDisjoint(ufmap, st) + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { + st match { + case d : DisjointStmt => + rewriteDisjoint(ufmap, d) case _ => - throw new AssertionError("Unknown rule: " + st.id.toString()) + throw new AssertionError("Unknown proof statement: " + st.toString()) } } + lazy val controlBlock : List[l.GenericProofCommand] = List( + l.GenericProofCommand( + l.Identifier("verify"), + List.empty, List((l.Identifier("countingProof"), "countingProof")), + Some(l.Identifier("v")), None), + l.GenericProofCommand( + l.Identifier("check"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_results"), + List.empty, List.empty, + None, None), + ) + + def process() : l.Module = { - val proofProc = module.procedures(0) - val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) - val countingOps = identifyCountOps(proofProcBody) + val countingOps = identifyCountOps(cntProof.stmts) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val newProofStmts = proofProcBody.map(st => rewriteAssert(ufMap, st)).flatten + val newProofStmts = cntProof.stmts.map(st => rewriteAssert(ufMap, st)).flatten val newProofProc = l.ProcedureDecl( - l.Identifier("newCountingProof"), proofProc.sig, + l.Identifier("countingProof"), + l.ProcedureSig(List.empty, List.empty), l.BlockStmt(List.empty, newProofStmts), - List.empty, List.empty, Set.empty, proofProc.annotations) - val prevDecls = module.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) - val moduleP = l.Module(module.id, + List.empty, List.empty, Set.empty, l.ProcedureAnnotations(Set.empty)) + val prevDecls = cntProof.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val moduleP = l.Module(cntProof.id, prevDecls ++ ufDecls ++ List(newProofProc), - module.cmds, module.notes) + controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 5cae24136..e37561569 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -159,7 +159,7 @@ object Operator { def oldInstance(c : OperatorApplication) = OperatorApplication(OldOperator(), List(c)) def history(c : Identifier, e : Expr) = OperatorApplication(HistoryOperator(), List(c, e)) } -trait Operator extends ASTNode { +sealed trait Operator extends ASTNode { def fixity : Int def isPolymorphic = false def isTemporal = false @@ -449,17 +449,6 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } -/** CountingOp is used in the model counting extension. */ -case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)]) extends Operator { - override def toString() = { - val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") - val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") - "#[(" + s1 + ") for (" + s2 + ") :: " - } - override def fixity = Operator.PREFIX - override val hashId = 1402 - override val md5hashCode = computeMD5Hash(xs, ys) -} // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { @@ -658,7 +647,7 @@ case class DistinctOp() extends Operator { override val hashId = 1709 override val md5hashCode = computeMD5Hash } -sealed abstract class Expr extends ASTNode { +abstract class Expr extends ASTNode { /** Is this value a statically-defined constant? */ def isConstant = false def isTemporal = false @@ -757,8 +746,6 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString - case CountingOp(_, _) => - op.toString() + operands(0).toString() + "]" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => @@ -1754,7 +1741,7 @@ case class GenericProofCommand( override def toString = { val nameStr = name.toString val paramStr = if (params.size > 0) { "[" + Utils.join(params.map(_.toString), ", ") + "]" } else { "" } - val argStr = if (args.size > 0) { "(" + Utils.join(args.map(_.toString), ", ") + ")" } else { "" } + val argStr = if (args.size > 0) { "(" + Utils.join(args.map(a => a._1.toString + " /* " + a._2 + "*/"), ", ") + ")" } else { "" } val resultStr = resultVar match { case Some(id) => id.toString + " = "; case None => "" } val objStr = argObj match { case Some(id) => id.toString + "->"; case None => "" } resultStr + objStr + nameStr + paramStr + argStr + ";" + " // " + position.toString diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 7cb7b1304..16e19f355 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -375,9 +375,6 @@ class UclidParser extends UclidTokenParsers with PackratParsers { KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | - ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { - case xs ~ ys ~ e => OperatorApplication(CountingOp(xs, ys), List(e)) - } | Id } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index fc0f06f82..0bba737f0 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,12 +1,12 @@ module main { - define f(n : integer) : boolean = n >= 5 && n <= 10; + define f(n : integer) : boolean = n >= 5 && n <= 20; define g(n : integer) : boolean = n >= 11 && n <= 20; - define h(n : integer) : boolean = n >= 5 && n <= 20; + define h(n : integer) : boolean = n >= 5 && n <= 10; proof { - assert disjoint: #[(n:integer) for () :: f(n)] == - #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; + assert disjoint: #[(n:integer) for ()] :: f(n) == + #[(n:integer)] :: g(n) + #[(n:integer)] :: h(n); } } From ad13306033fdfe52f93b6a30102f27e1d1e20868 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 15 Apr 2020 02:38:12 +0530 Subject: [PATCH 015/119] We have added support for the range rule. --- .../extensions/modelcounts/UMCLanguage.scala | 112 +++++++++++++++++- .../extensions/modelcounts/UMCParser.scala | 37 ++++-- .../extensions/modelcounts/UMCRewriter.scala | 14 ++- src/main/scala/uclid/lang/Scope.scala | 31 +++++ test/modelcounter/hello.ucl | 12 +- 5 files changed, 188 insertions(+), 18 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 32a5cac95..06ad4a249 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -59,17 +59,92 @@ case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, } /** This is the base class for all the "statements" in the proof. */ -abstract class Statement extends l.ASTNode { +sealed abstract class Statement extends l.ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def toLines : List[String] - def expressions : Seq[CountingOp] + def countingOps : Seq[CountingOp] + def expressions: Seq[l.Expr] + def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] } case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def countingOps = Seq(e1, e2, e3) override def expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(DisjointStmt(op1, op2, op3)) + case _ => None + } + } +} + +case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { + lazy val lb : l.Expr = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + lb + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + lazy val ub : l.Expr = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + ub + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + lazy val v : l.Identifier = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + assert(u == v) + l.Identifier(v) + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + override val hashId = 130002 + override val md5hashCode = computeMD5Hash(op, cnt) + override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) + override def countingOps = Seq(op) + override def expressions = Seq(op, cnt) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(op.e), rewriter(cnt)) match { + case (Some(ep), Some(cntp)) => + val op1 = CountingOp(op.xs, op.ys, ep) + Some(RangeStmt(op1, cntp)) + case _ => None + } + } +} +case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130003 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) + override def countingOps = Seq(e) + override def expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } } case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { @@ -80,9 +155,18 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S Utils.join(stmts.map(st => " " + st.toString()), "\n") + "\n }\n}" } + def rewriteStatments(rewriter : l.ASTRewriter) : CountingProof = { + val ctx = decls.foldLeft(l.Scope.empty)((acc, d) => acc + d) + def rewriterFn(expr : l.Expr) : Option[l.Expr] = { + rewriter.visitExpr(expr, ctx) + } + val stmtsP = stmts.map(st => st.rewrite(rewriterFn)).flatten + CountingProof(id, decls, stmtsP) + } override val hashId = 131001 override val md5hashCode = computeMD5Hash(id, decls, stmts) } + /** Helpers to construct UCLID5 expressions. */ object UMCExpressions { // Helper functions to more easily construct expressions. @@ -114,4 +198,28 @@ object UMCExpressions { def plus(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.AddOp(), List(e1, e2)) } + + def minus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.SubOp(), List(e1, e2)) + } + + def le(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntLEOp(), List(e1, e2)) + } + + def lt(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntLTOp(), List(e1, e2)) + } + + def rng(e1 : l.Expr, e2 : l.Expr, e3 : l.Expr) = { + and(le(e1, e2), lt(e2, e3)) + } + + def ite(c : l.Expr, e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ITEOp(), List(c, e1, e2)) + } + + def max(e1 : l.Expr, e2 : l.Expr) = { + ite(lt(e1, e2), e2, e1) + } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 2e7f2888b..eb501e3a9 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -42,32 +42,48 @@ package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.Utils import uclid.lang.Identifier +import uclid.lang.Type import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lazy val KwDisjoint = "disjoint" + lazy val KwConstLB = "constLB" - lexical.reserved += (KwProof, KwDisjoint) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) + lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { + case xs ~ ys => (xs, ys) + } | + ("#[" ~> IdTypeList) <~ "]" ~ "::" ^^ { + case xs => (xs, List.empty) + } + lazy val CountingExpr : PackratParser[CountingOp] = positioned { - ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { - case xs ~ ys ~ e => CountingOp(xs, ys, e) - } | - ("#[" ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { - case xs ~ e => CountingOp(xs, List.empty, e) + CountingOpPrefix ~ ("(" ~> Expr <~ ")") ^^ { + case xs ~ e => CountingOp(xs._1, xs._2, e) } } lazy val AssertStmt: PackratParser[Statement] = positioned { KwAssert ~ KwDisjoint ~ ":" ~> - (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ - { + (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + } | + KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { + case e1 ~ e2 => { + RangeStmt(e1, e2) + } + } | + KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstLbStmt(e, v) + } } } lazy val ProofStmt: PackratParser[Statement] = @@ -94,10 +110,11 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() - val filenameAdderPass = new l.AddFilenameRewriter(None) + val rewriter = new l.RewriteDefines() def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString - parserObj.parseUMCModel(filePath, text) + val model = parserObj.parseUMCModel(filePath, text) + model.rewriteStatments(rewriter) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 8f1ef82c3..16ab729ea 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -53,7 +53,7 @@ class UMCRewriter(cntProof : CountingProof) { /** Finding all the counting operators in a list of assert statements. */ def identifyCountOps(proofBlk: List[Statement]) : CountingOpSet = { proofBlk.foldLeft(Set.empty[CountingOp]) { - (acc, st) => acc ++ st.expressions.toSet + (acc, st) => acc ++ st.countingOps.toSet } } @@ -144,10 +144,22 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.op) + val ufn = _apply(ufMap(st.op)) + val assumeExpr = E.forall(args, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + val assertExpr = E.forall(args, E.eq(ufn, st.cnt)) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + List(assumeStmt, assertStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { case d : DisjointStmt => rewriteDisjoint(ufmap, d) + case r : RangeStmt => + rewriteRange(ufmap, r) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/src/main/scala/uclid/lang/Scope.scala b/src/main/scala/uclid/lang/Scope.scala index 5fda03d3c..91b1b9a0e 100644 --- a/src/main/scala/uclid/lang/Scope.scala +++ b/src/main/scala/uclid/lang/Scope.scala @@ -244,6 +244,37 @@ case class Scope ( Scope(map + (m.id -> Scope.ModuleDefinition(m)), module, procedure, cmd, environment, parent) } + def +(d: Decl) : Scope = { + val mapP = d match { + case instD : InstanceDecl => + Scope.addToMap(map, Scope.Instance(instD)) + case ProcedureDecl(id, sig, _, _, _, _, _) => Scope.addToMap(map, Scope.Procedure(id, sig.typ)) + case TypeDecl(id, typ) => Scope.addToMap(map, Scope.TypeSynonym(id, typ)) + case StateVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.StateVar(id, typ))) + case InputVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.InputVar(id, typ))) + case OutputVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.OutputVar(id, typ))) + case SharedVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.SharedVar(id, typ))) + case ConstantLitDecl(id, lit) => Scope.addToMap(map, Scope.ConstantLit(id, lit)) + case ConstantsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.ConstantVar(id, typ))) + case GrammarDecl(id, sig, _) => Scope.addToMap(map, Scope.Grammar(id, sig.typ)) + case FunctionDecl(id, sig) => Scope.addToMap(map, Scope.Function(id, sig.typ)) + case SynthesisFunctionDecl(id, sig, _, _, _) => Scope.addToMap(map, Scope.Function(id, sig.typ)) // FIXME + case DefineDecl(id, sig, expr) => Scope.addToMap(map, Scope.Define(id, sig.typ, DefineDecl(id, sig, expr))) + case SpecDecl(id, expr, params) => Scope.addToMap(map, Scope.SpecVar(id, expr, params)) + case AxiomDecl(sId, expr, params) => sId match { + case Some(id) => Scope.addToMap(map, Scope.AxiomVar(id, expr, params)) + case None => map + } + //case ModuleConstantsImportDecl(id) => Scope.addToMap(mapAcc, Scope.ConstantsImport(id)) + //case ModuleFunctionsImportDecl(id) => Scope.addToMap(mapAcc, Scope.FunctionsImport(id)) + case ModuleConstantsImportDecl(_) => map + case ModuleFunctionsImportDecl(_) => map + case ModuleTypesImportDecl(_) | + ModuleDefinesImportDecl(_) | + InitDecl(_) | NextDecl(_) => map + } + Scope(mapP, None, None, None, environment, parent) + } /** Return a new context with the declarations in this module added to it. */ def +(m: Module) : Scope = { Utils.assert(module.isEmpty, "A module was already added to this Context.") diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 0bba737f0..0d45eb0b3 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,12 +1,14 @@ module main { - define f(n : integer) : boolean = n >= 5 && n <= 20; - define g(n : integer) : boolean = n >= 11 && n <= 20; - define h(n : integer) : boolean = n >= 5 && n <= 10; + define f(n : integer) : boolean = 5 <= n < 21; + define g(n : integer) : boolean = 11 <= n < 21; + define h(n : integer) : boolean = 5 <= n < 11; proof { - assert disjoint: #[(n:integer) for ()] :: f(n) == - #[(n:integer)] :: g(n) + #[(n:integer)] :: h(n); + assert range: #[(n:integer)] :: (f(n)) == 16; + assert constLB: #[(n:integer)] :: (g(n)) >= 5; + assert disjoint: #[(n:integer) for ()] :: (f(n)) == + #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } From d1b9ae3d3ae1432e49d4194fed48beac0355e71a Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Thu, 16 Apr 2020 01:00:00 +0530 Subject: [PATCH 016/119] constlb and constub rules. --- .../extensions/modelcounts/UMCLanguage.scala | 38 ++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 8 +++- .../extensions/modelcounts/UMCRewriter.scala | 44 +++++++++++++++++++ src/main/scala/uclid/lang/UclidLanguage.scala | 2 +- test/modelcounter/hello.ucl | 2 + 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 06ad4a249..9264e2679 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -147,6 +147,22 @@ case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { } } +case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130004 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) + override def countingOps = Seq(e) + override def expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + @@ -175,6 +191,11 @@ object UMCExpressions { l.OperatorApplication(op, List(e)) } + def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ExistsOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + def and(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) } @@ -210,6 +231,15 @@ object UMCExpressions { def lt(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IntLTOp(), List(e1, e2)) } + + def ge(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntGEOp(), List(e1, e2)) + } + + def gt(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntGTOp(), List(e1, e2)) + } + def rng(e1 : l.Expr, e2 : l.Expr, e3 : l.Expr) = { and(le(e1, e2), lt(e2, e3)) @@ -222,4 +252,12 @@ object UMCExpressions { def max(e1 : l.Expr, e2 : l.Expr) = { ite(lt(e1, e2), e2, e1) } + + def distinct(es : List[l.Expr]) = { + if (es.size == 1) { + es(0) + } else { + l.OperatorApplication(l.DistinctOp(), es) + } + } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index eb501e3a9..300386934 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -50,8 +50,9 @@ class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lazy val KwDisjoint = "disjoint" lazy val KwConstLB = "constLB" + lazy val KwConstUB = "constUB" - lexical.reserved += (KwProof, KwDisjoint, KwConstLB) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) @@ -84,6 +85,11 @@ class UMCParser extends l.UclidParser { case e ~ v => { ConstLbStmt(e, v) } + } | + KwAssert ~ KwConstUB ~ ":" ~> CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstUbStmt(e, v) + } } } lazy val ProofStmt: PackratParser[Statement] = diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 16ab729ea..534560bab 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -154,12 +154,56 @@ class UMCRewriter(cntProof : CountingProof) { List(assumeStmt, assertStmt) } + def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.e) + val argVars = args.map(a => a._1) + val cnt = st.v.value.toInt + val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) + val existsVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] + val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) + val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val query = E.exists(existsVars, E.and(conjunction, distincts)) + val assertStmt = l.AssertStmt(query, None, List.empty) + val ufn = _apply(ufMap(st.e)) + val assumeExpr = E.forall(args, E.ge(ufn, st.v)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + + def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.e) + val argVars = args.map(a => a._1) + val cnt = st.v.value.toInt + val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) + val vs : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] + val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) + val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val query = E.not(E.exists(vs, E.and(conjunction, distincts))) + val assertStmt = l.AssertStmt(query, None, List.empty) + val ufn = _apply(ufMap(st.e)) + val assumeExpr = E.forall(args, E.lt(ufn, st.v)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { case d : DisjointStmt => rewriteDisjoint(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) + case lb : ConstLbStmt => + rewriteConstLb(ufmap, lb) + case ub : ConstUbStmt => + rewriteConstUb(ufmap, ub) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index e37561569..1c495acb3 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -643,7 +643,7 @@ case class GetNextValueOp() extends Operator { } case class DistinctOp() extends Operator { override def toString = "distinct" - override def fixity = Operator.INFIX + override def fixity = Operator.PREFIX override val hashId = 1709 override val md5hashCode = computeMD5Hash } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 0d45eb0b3..a8dfe8849 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -7,6 +7,8 @@ module main { proof { assert range: #[(n:integer)] :: (f(n)) == 16; assert constLB: #[(n:integer)] :: (g(n)) >= 5; + assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; + assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; assert disjoint: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } From 116a4960e98ad5d008c45419d204320ed44747a7 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Fri, 17 Apr 2020 19:27:56 +0530 Subject: [PATCH 017/119] cover properties properly support. --- src/main/scala/uclid/AssertionTree.scala | 16 +++++++++--- src/main/scala/uclid/SymbolicSimulator.scala | 6 ++--- .../extensions/modelcounts/UMCLanguage.scala | 4 +-- .../extensions/modelcounts/UMCRewriter.scala | 2 +- src/main/scala/uclid/lang/ASTVistors.scala | 5 ++-- .../uclid/lang/ModularProductProgram.scala | 13 ++++------ .../scala/uclid/lang/ModuleTypeChecker.scala | 2 +- .../uclid/lang/PrimedAssignmentChecker.scala | 10 ++++---- src/main/scala/uclid/lang/UclidLanguage.scala | 25 +++++++++++-------- src/main/scala/uclid/lang/UclidParser.scala | 4 +++ test/debug/mc.ucl | 17 +++++++++++++ test/modelcounter/counting_2n.ucl | 19 ++++++++++++++ 12 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 test/debug/mc.ucl create mode 100644 test/modelcounter/counting_2n.ucl diff --git a/src/main/scala/uclid/AssertionTree.scala b/src/main/scala/uclid/AssertionTree.scala index bd78d22c3..ec6a6a249 100644 --- a/src/main/scala/uclid/AssertionTree.scala +++ b/src/main/scala/uclid/AssertionTree.scala @@ -175,10 +175,18 @@ class AssertionTree { solver.curAssertName = e.name solver.curAssertLabel = e.label val sat = solver.check(getModel) - val result = sat.result match { - case Some(true) => smt.SolverResult(Some(false), sat.model) - case Some(false) => smt.SolverResult(Some(true), sat.model) - case None => smt.SolverResult(None, None) + val result = if (e.decorators.contains(SATOnlyDecorator)) { + sat.result match { + case Some(true) => smt.SolverResult(Some(true), sat.model) + case Some(false) => smt.SolverResult(Some(false), sat.model) + case None => smt.SolverResult(None, None) + } + } else { + sat.result match { + case Some(true) => smt.SolverResult(Some(false), sat.model) + case Some(false) => smt.SolverResult(Some(true), sat.model) + case None => smt.SolverResult(None, None) + } } solver.pop() Some(CheckResult(e, result)) diff --git a/src/main/scala/uclid/SymbolicSimulator.scala b/src/main/scala/uclid/SymbolicSimulator.scala index e129a7b21..7e6a7a3c4 100644 --- a/src/main/scala/uclid/SymbolicSimulator.scala +++ b/src/main/scala/uclid/SymbolicSimulator.scala @@ -1741,7 +1741,7 @@ class SymbolicSimulator (module : Module) { frameLog.debug("symbolTable: %s".format(symbolTable.toString())) s match { case SkipStmt() => return symbolTable - case AssertStmt(e, id, params) => + case AssertStmt(e, id, decorators) => val frameTableP = frameTable.clone() frameTableP += symbolTable val simTable = ArrayBuffer(frameTableP) @@ -1753,7 +1753,7 @@ class SymbolicSimulator (module : Module) { val assert = AssertInfo( assertionName, label, simTable.clone(), scope, frameNumber, pathCondExpr, - assertExpr, List.empty, s.position) + assertExpr, decorators, s.position) assertLog.debug("Assertion: {}", e.toString) assertLog.debug("VC: {}", assertExpr.toString) frameLog.debug("FrameTableSize: {}", frameTableP.size) @@ -1829,7 +1829,7 @@ class SymbolicSimulator (module : Module) { def writeSet(stmt: Statement) : Set[Identifier] = stmt match { case SkipStmt() => Set.empty - case AssertStmt(e, id, params) => Set.empty + case AssertStmt(e, id, _) => Set.empty case AssumeStmt(e, id) => Set.empty case HavocStmt(h) => h match { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 9264e2679..304d0a872 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -254,8 +254,8 @@ object UMCExpressions { } def distinct(es : List[l.Expr]) = { - if (es.size == 1) { - es(0) + if (es.size <= 1) { + l.BoolLit(true) } else { l.OperatorApplication(l.DistinctOp(), es) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 534560bab..c5c62f063 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -167,7 +167,7 @@ class UMCRewriter(cntProof : CountingProof) { val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) val query = E.exists(existsVars, E.and(conjunction, distincts)) - val assertStmt = l.AssertStmt(query, None, List.empty) + val assertStmt = l.AssertStmt(query, None, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) val assumeExpr = E.forall(args, E.ge(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) diff --git a/src/main/scala/uclid/lang/ASTVistors.scala b/src/main/scala/uclid/lang/ASTVistors.scala index 96916db8b..8a9ed83cd 100644 --- a/src/main/scala/uclid/lang/ASTVistors.scala +++ b/src/main/scala/uclid/lang/ASTVistors.scala @@ -743,7 +743,6 @@ class ASTAnalyzer[T] (_passName : String, _pass: ReadOnlyPass[T]) extends ASTAna case None => result case Some(id) => visitIdentifier(id, result, context) } - result = st.params.foldLeft(result)((acc, e) => visitExpr(e, acc, context)) val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment result = visitExpr(st.e, result, context.withEnvironment(envP)) result = pass.applyOnAssert(TraversalDirection.Up, st, result, context) @@ -1610,10 +1609,10 @@ class ASTRewriter (_passName : String, _pass: RewritePass, setFilename : Boolean def visitAssertStatement(st : AssertStmt, context : Scope) : Option[Statement] = { val idP = st.id.flatMap(id => visitIdentifier(id, context)) - val paramsP = st.params.map(p => visitExpr(p, context)).flatten val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment + // TODO: implement visitDecorator val stP = visitExpr(st.e, context.withEnvironment(envP)).flatMap((e) => { - pass.rewriteAssert(AssertStmt(e, idP, paramsP), context) + pass.rewriteAssert(AssertStmt(e, idP, st.decorators), context) }) return ASTNode.introducePos(setPosition, setFilename, stP, st.position) } diff --git a/src/main/scala/uclid/lang/ModularProductProgram.scala b/src/main/scala/uclid/lang/ModularProductProgram.scala index ad149d3cb..5da7956df 100644 --- a/src/main/scala/uclid/lang/ModularProductProgram.scala +++ b/src/main/scala/uclid/lang/ModularProductProgram.scala @@ -524,7 +524,7 @@ class ModularProductProgramPass extends RewritePass { } } - case AssertStmt(expr, id, params) => + case AssertStmt(expr, id, decorators) => val activationVariableArray = helperObj.mapOfActivationVariables(currentScope) val emptyVarsList: List[BlockVarsDecl] = List() expr match { @@ -546,8 +546,7 @@ class ModularProductProgramPass extends RewritePass { andCondition = Operator.and(andCondition, checkActVarCondition) } val renamedExpression = getRenamedExpr(expr, context, k) - val renamedParams = params.map(p => getRenamedExpr(p, context, k)) - val newAssertStatement = AssertStmt(renamedExpression, id, renamedParams) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -561,8 +560,7 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val renamedParams = params.map(p => getRenamedExpr(p, context, i)) - val newAssertStatement = AssertStmt(renamedExpression, id, params) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -578,8 +576,7 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val renamedParams = params.map(p => getRenamedExpr(p, context, i)) - val newAssertStatement = AssertStmt(renamedExpression, id, params) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -1097,7 +1094,7 @@ class ModularProductProgramPass extends RewritePass { newstmts += oldstmts.head } - case AssertStmt(expr, id, params) => + case AssertStmt(expr, id, decorators) => expr match { case OperatorApplication(op, operands) => val hasHyperSelect = isHyperSelectPresent(op, operands) diff --git a/src/main/scala/uclid/lang/ModuleTypeChecker.scala b/src/main/scala/uclid/lang/ModuleTypeChecker.scala index 5917d55fc..9fdeeb51c 100644 --- a/src/main/scala/uclid/lang/ModuleTypeChecker.scala +++ b/src/main/scala/uclid/lang/ModuleTypeChecker.scala @@ -50,7 +50,7 @@ class ModuleTypeCheckerPass extends ReadOnlyPass[Set[ModuleError]] in } else { st match { - case AssertStmt(e, id, params) => + case AssertStmt(e, id, _) => val eType = exprTypeChecker.typeOf(e, context) if (!eType.isBool) { in + ModuleError("Assertion expression must be of Boolean or Temporal type", st.position) diff --git a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala index 1244a5234..09777d377 100644 --- a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala +++ b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala @@ -100,11 +100,11 @@ class PrimedAssignmentCheckerPass extends ReadOnlyPass[Set[ModuleError]] } } st match { - case IfElseStmt(_, _, _) | - ForStmt(_, _, _, _) | WhileStmt(_, _, _) | - CaseStmt(_) | SkipStmt() | - AssertStmt(_, _, _) | AssumeStmt(_, _) | - HavocStmt(_) | BlockStmt(_, _) => + case IfElseStmt(_, _, _) | + ForStmt(_, _, _, _) | WhileStmt(_, _, _) | + CaseStmt(_) | SkipStmt() | + AssertStmt(_, _, _) | AssumeStmt(_, _) | + HavocStmt(_) | BlockStmt(_, _) => in case ModuleCallStmt(_) => checkParallelConstruct("next") diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 1c495acb3..08ea601a9 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -846,17 +846,19 @@ case object CoverDecorator extends ExprDecorator { override val hashId = 2605 override val md5hashCode = computeMD5Hash } +case object SATOnlyDecorator extends ExprDecorator { + override def toString = "satonly" + override val hashId = 2606 + override val md5hashCode = computeMD5Hash +} object ExprDecorator { /** Factory constructor. */ def parse(e : Expr) : ExprDecorator = { val dec = e match { - case Identifier(id) => - if (id == "LTL") { - LTLExprDecorator - } else { - UnknownDecorator(e.toString) - } + case Identifier("LTL") => LTLExprDecorator + case Identifier("cover") => CoverDecorator + case Identifier("SATOnly") => SATOnlyDecorator case _ => UnknownDecorator(e.toString) } dec.pos = e.pos @@ -1152,14 +1154,15 @@ case class SkipStmt() extends Statement { override val hashId = 3000 override val md5hashCode = computeMD5Hash } -case class AssertStmt(e: Expr, id : Option[Identifier], params: List[Expr]) extends Statement { +case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDecorator]) extends Statement { override def toLines = { - val paramStr = if (params.size > 0) { - " [" + Utils.join(params.map(_.toString()), ", ") + "] " + val name = "assert" + val decoratorStr = if (decorators.size > 0) { + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] " } else { "" } val prefix = id match { - case Some(n) => "assert " + n.toString() + paramStr + ": " - case None => "assert " + paramStr + case Some(n) => name + " " + n.toString() + decoratorStr + ": " + case None => name + " " + decoratorStr } List(prefix + e.toString() + "; // " + position.toString) } diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 16e19f355..34be86921 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -460,6 +460,10 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Statement: PackratParser[Statement] = positioned { KwSkip <~ ";" ^^ { case _ => SkipStmt() } | KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None, List.empty) } | + KwAssert ~> ("[" ~> IdList <~ "]" ~ ":") ~ Expr <~ ";" ^^ { + case ids ~ e => + AssertStmt(e, None, ids.map(ExprDecorator.parse(_))) + } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ diff --git a/test/debug/mc.ucl b/test/debug/mc.ucl new file mode 100644 index 000000000..c50871535 --- /dev/null +++ b/test/debug/mc.ucl @@ -0,0 +1,17 @@ +module main{ + var arr_1: [integer]boolean; + var n_1: integer; + + init { + assert [cover, SATOnly]: + (true && (n_1 > 0) ==> + (forall (i : integer) :: (((i < 0) || (i >= n_1)) ==> !((arr_1)[i]))) && + (exists (i : integer) :: (((0 <= i) && (i < n_1)) && (arr_1)[i]))); + } + + control { + v = unroll(1); + check; + print_results; + } +} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl new file mode 100644 index 000000000..038d4808c --- /dev/null +++ b/test/modelcounter/counting_2n.ucl @@ -0,0 +1,19 @@ +module main { + + define U(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + define V(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + define W(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: !arr[i]); + + proof { + assert disjoint: #[(n : integer) for (arr: [integer]boolean)] :: (U(arr, n)) == + #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) + + #[(n : integer) for (arr: [integer]boolean)] :: (W(arr, n)); + assert constLB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) >= 1; + // assert constUB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) < 2; + } +} + From 818ea8277cc48e05d8c554f20f123d20d7ba3ef3 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 18 Apr 2020 23:45:58 +0530 Subject: [PATCH 018/119] New rewriter. --- .../extensions/modelcounts/UMCLanguage.scala | 102 ++++++++++++++++-- .../extensions/modelcounts/UMCParser.scala | 43 +++++++- .../extensions/modelcounts/UMCRewriter.scala | 60 ++++++----- .../scala/uclid/lang/ASTVisitorUtils.scala | 1 + src/main/scala/uclid/lang/UclidParser.scala | 2 +- test/modelcounter/counting_2n.ucl | 29 ++--- 6 files changed, 184 insertions(+), 53 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 304d0a872..af0b17ef5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -37,9 +37,11 @@ */ package uclid.extensions.modelcounts + import uclid.UclidMain import uclid.{lang => l} import uclid.Utils +import uclid.Memo /** CountingOp is a new operator we introduce for the UMC extension. */ @@ -58,21 +60,44 @@ case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, override val md5hashCode = computeMD5Hash(xs, ys) } + /** This is the base class for all the "statements" in the proof. */ sealed abstract class Statement extends l.ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def toLines : List[String] - def countingOps : Seq[CountingOp] - def expressions: Seq[l.Expr] + val countingOps : Seq[CountingOp] + val expressions: Seq[l.Expr] def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] } +case class AssertStmt(e : l.Expr) extends Statement { + override val hashId = 130000 + override val md5hashCode = computeMD5Hash(e) + override def toLines = List("assert " + e.toString()) + override val countingOps = { + def isCountingOp(e : l.Expr) = { + e match { + case CountingOp(_, _, _) => true + case _ => false + } + } + UMCExpressions.findSubExpressions(e, isCountingOp _).map(_.asInstanceOf[CountingOp]).toSeq + } + override val expressions = Seq(e) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + rewriter(e) match { + case Some(eP) => Some(AssertStmt(eP)) + case None => None + } + } +} + case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) - override def countingOps = Seq(e1, e2, e3) - override def expressions = Seq(e1, e2, e3) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { case (Some(e1p), Some(e2p), Some(e3p)) => @@ -120,8 +145,8 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { override val hashId = 130002 override val md5hashCode = computeMD5Hash(op, cnt) override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) - override def countingOps = Seq(op) - override def expressions = Seq(op, cnt) + override val countingOps = Seq(op) + override val expressions = Seq(op, cnt) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(op.e), rewriter(cnt)) match { case (Some(ep), Some(cntp)) => @@ -135,8 +160,8 @@ case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { override val hashId = 130003 override val md5hashCode = computeMD5Hash(e, v) override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) - override def countingOps = Seq(e) - override def expressions = Seq(e, v) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e.e), rewriter(v)) match { case (Some(e1p), Some(e2p)) => @@ -151,8 +176,8 @@ case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { override val hashId = 130004 override val md5hashCode = computeMD5Hash(e, v) override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) - override def countingOps = Seq(e) - override def expressions = Seq(e, v) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e.e), rewriter(v)) match { case (Some(e1p), Some(e2p)) => @@ -260,4 +285,61 @@ object UMCExpressions { l.OperatorApplication(l.DistinctOp(), es) } } + + def _findSubExpressions(e : l.Expr, fn : l.Expr => Boolean) : Set[l.Expr] = + findSubExpressions((e, fn)) + + val findSubExpressions = new Memo[(l.Expr, l.Expr => Boolean), Set[l.Expr]]( + (p : (l.Expr, l.Expr => Boolean)) => { + val expr = p._1 + val fn = p._2 + val subExprs : Set[l.Expr] = expr match { + case _ : l.Literal => Set.empty + case _ : l.Identifier => Set.empty + case _ : l.ExternalIdentifier => Set.empty + case l.ConstArray(e, t) => _findSubExpressions(e, fn) + case l.Tuple(es) => es.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.OperatorApplication(op, args) => + args.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.Lambda(ids, e) => _findSubExpressions(e, fn) + case l.FuncApplication(e1, e2) => e2.foldLeft(_findSubExpressions(e1, fn))((acc, e) => acc ++ _findSubExpressions(e, fn)) + case CountingOp(xs, ys, e) => _findSubExpressions(e, fn) + } + if (fn(expr)) { subExprs + expr} + else { subExprs } + } + ) } + +class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { + val rewrite : Memo[l.Expr, l.Expr] = new Memo[l.Expr, l.Expr]( + exp => { + val expP : l.Expr = exp match { + case _ : l.Literal | _ : l.Identifier | _ : l.ExternalIdentifier => + exp + case l.ConstArray(e, t) => + val eP : l.Expr = rewrite(e) + l.ConstArray(eP, t) + case l.Tuple(es) => + val esP = es.map(e => rewrite(e)) + l.Tuple(esP) + case l.OperatorApplication(op, args) => + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + case l.Lambda(ids, exp) => + val expP = rewrite(exp) + l.Lambda(ids, exp) + case l.FuncApplication(e1, e2s) => + val e1p = rewrite(e1) + val e2sp = e2s.map(e2 => rewrite(e2)) + l.FuncApplication(e1p, e2sp) + case CountingOp(xs, ys, e) => + val eP = rewrite(e) + CountingOp(xs, ys, eP) + } + rwMap.get(expP) match { + case Some(repl) => repl + case None => expP + } + }) +} \ No newline at end of file diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 300386934..1cbcdb48b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -71,7 +71,24 @@ class UMCParser extends l.UclidParser { } } - lazy val AssertStmt: PackratParser[Statement] = positioned { + lazy val C0: PackratParser[l.Expr] = positioned { CountingExpr | E1 } + + override lazy val E15: PackratParser[l.Expr] = positioned { + Literal | + "{" ~> Expr ~ rep("," ~> Expr) <~ "}" ^^ {case e ~ es => l.Tuple(e::es)} | + KwIf ~> ("(" ~> Expr <~ ")") ~ (KwThen ~> Expr) ~ (KwElse ~> Expr) ^^ { + case expr ~ thenExpr ~ elseExpr => l.OperatorApplication(l.ITEOp(), List(expr, thenExpr, elseExpr)) + } | + ConstArray | + KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => l.Lambda(idtyps, expr) } | + "(" ~> CExpr <~ ")" | + Id <~ OpPrime ^^ { case id => l.OperatorApplication(l.GetNextValueOp(), List(id)) } | + Id + } + + lazy val CExpr: PackratParser[l.Expr] = positioned { C0 } + + lazy val Stmt: PackratParser[Statement] = positioned { KwAssert ~ KwDisjoint ~ ":" ~> (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) @@ -90,10 +107,13 @@ class UMCParser extends l.UclidParser { case e ~ v => { ConstUbStmt(e, v) } + } | + KwAssert ~> CExpr <~ ";" ^^ { + case e => AssertStmt(e) } } lazy val ProofStmt: PackratParser[Statement] = - positioned ( AssertStmt ); + positioned ( Stmt ); lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } @@ -114,9 +134,26 @@ class UMCParser extends l.UclidParser { } } +class RewriteDefines extends l.RewriteDefines { + override def visitExpr(e : l.Expr, context : l.Scope) : Option[l.Expr] = { + val eP = (e match { + case i : Identifier => visitIdentifier(i, context) + case eId : l.ExternalIdentifier => visitExternalIdentifier(eId, context) + case lit : l.Literal => visitLiteral(lit, context) + case rec : l.Tuple => visitTuple(rec, context) + case opapp : l.OperatorApplication => visitOperatorApp(opapp, context) + case a : l.ConstArray => visitConstArray(a, context) + case fapp : l.FuncApplication => visitFuncApp(fapp, context) + case lambda : l.Lambda => visitLambda(lambda, context) + case cntOp : CountingOp => + visitExpr(cntOp.e, context).flatMap(eP => Some(CountingOp(cntOp.xs, cntOp.ys, eP))) + }).flatMap(pass.rewriteExpr(_, context)) + return l.ASTNode.introducePos(true, true, eP, e.position) + } +} object UMCParser { val parserObj = new UMCParser() - val rewriter = new l.RewriteDefines() + val rewriter = new RewriteDefines() def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index c5c62f063..98804d3eb 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -42,7 +42,9 @@ import uclid.{lang => l} import uclid.Memo import uclid.extensions.modelcounts.{UMCExpressions => E} import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} - +import uclid.lang.BlockStmt + + class UMCRewriter(cntProof : CountingProof) { /* We will be using this set in a number of places. */ type CountingOpSet = Set[CountingOp] @@ -125,6 +127,14 @@ class UMCRewriter(cntProof : CountingProof) { def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } + + def rewriteAssert(ufMap : UFMap, st : AssertStmt) : List[l.Statement] = { + val rewriter = new ExprRewriter(ufMap.map(p => (p._1 -> _apply(p._2))).toMap) + val eP = rewriter.rewrite(st.e) + val assertStmt = l.AssertStmt(eP, None, List.empty) + List(assertStmt) + } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { val o1 = st.e1 val o2 = st.e2 @@ -154,48 +164,44 @@ class UMCRewriter(cntProof : CountingProof) { List(assumeStmt, assertStmt) } - def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.e) - val argVars = args.map(a => a._1) - val cnt = st.v.value.toInt - val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + def getCnstBoundStmt(ufMap : UFMap, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { + val cntArgs = e.xs + val argVars = cntArgs.map(a => a._1) + val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) - val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) - val existsVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(e.e, rwMap, l.Scope.empty)) + val newVars : List[(l.Identifier, l.Type)] = + argsListP.map(argsP => (cntArgs zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten ++ + e.ys + val blkDecls = newVars.map(p => l.BlockVarsDecl(List(p._1), p._2)) val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) - val query = E.exists(existsVars, E.and(conjunction, distincts)) - val assertStmt = l.AssertStmt(query, None, List(l.CoverDecorator, l.SATOnlyDecorator)) + val query = E.and(conjunction, distincts) + val assertStmt = l.AssertStmt(query, None, decorators) + BlockStmt(blkDecls, List(assertStmt)) + } + def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { + val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(args, E.ge(ufn, st.v)) + val assumeExpr = E.forall(extractCountingArgs(st.e), E.ge(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) + List(blkStmt, assumeStmt) } def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.e) - val argVars = args.map(a => a._1) - val cnt = st.v.value.toInt - val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) - val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) - val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) - val vs : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten - val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] - val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) - val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] - val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) - val query = E.not(E.exists(vs, E.and(conjunction, distincts))) - val assertStmt = l.AssertStmt(query, None, List.empty) + val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(args, E.lt(ufn, st.v)) + val assumeExpr = E.forall(extractCountingArgs(st.e), E.lt(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) + List(blkStmt, assumeStmt) } def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { + case a : AssertStmt => + rewriteAssert(ufmap, a) case d : DisjointStmt => rewriteDisjoint(ufmap, d) case r : RangeStmt => diff --git a/src/main/scala/uclid/lang/ASTVisitorUtils.scala b/src/main/scala/uclid/lang/ASTVisitorUtils.scala index 3469b5cd7..d386fe0ea 100644 --- a/src/main/scala/uclid/lang/ASTVisitorUtils.scala +++ b/src/main/scala/uclid/lang/ASTVisitorUtils.scala @@ -130,6 +130,7 @@ object ExprRewriter val rewriter = new ASTRewriter("", new ExprRewriterPass(rewrites)) rewriter.visitLhs(lhs, context).get } + } class ExprRewriter(name: String, rewrites : Map[Expr, Expr]) diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 34be86921..ca1f33574 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -276,7 +276,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Pattern : PackratParser[(lang.Identifier, List[List[lang.Expr]])] = Id ~ ("[" ~> PatternList <~ "]") ^^ { case id ~ pats => (id, pats) } - lazy val E1: PackratParser[Expr] = + lazy val E1: PackratParser[Expr] = KwForall ~> IdTypeList ~ Pattern.? ~ ("::" ~> E1) ^^ { case ids ~ pat ~ expr => { pat match { diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 038d4808c..9c60eb551 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -1,19 +1,24 @@ module main { - define U(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); - define V(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); - define W(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: !arr[i]); + define U(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + define V(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + define W(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: !arr[i]); proof { - assert disjoint: #[(n : integer) for (arr: [integer]boolean)] :: (U(arr, n)) == - #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) + - #[(n : integer) for (arr: [integer]boolean)] :: (W(arr, n)); - assert constLB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) >= 1; - // assert constUB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) < 2; + assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == + #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); + assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; + assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; + assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= + #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * + #[(i : integer) for (n : integer)] :: (0 <= i < 2) + using arr[n -> i]; } } From c5452e44c079a241883ed3f6a45a08b72a53ccae Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 18 Apr 2020 23:54:22 +0530 Subject: [PATCH 019/119] a few minor additions to counting_2n. --- test/modelcounter/counting_2n.ucl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 9c60eb551..96067a266 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -7,18 +7,27 @@ module main { (exists (i : integer) :: 0 <= i < n && arr[i]); define W(arr : [integer]boolean, n : integer) : boolean = (forall (i : integer) :: !arr[i]); + define X(i : integer) : boolean = + (0 <= i < 2); proof { assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); + // #arr: W(arr, n) == 1 assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + // prove #i: X(i) == 2 + assert constLB: #[(i : integer) for (n : integer)] :: (X(i)) >= 2; + assert constUB: #[(i : integer) for (n : integer)] :: (X(i)) < 3; + assert forall (i : integer, n : integer) :: (#[(i : integer) for (n : integer)] :: (X(i))) == 2; + /* assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * #[(i : integer) for (n : integer)] :: (0 <= i < 2) using arr[n -> i]; + */ } } From 78e49f2e53a2842be51d70dada19e7ee420d8746 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 20 Apr 2020 03:04:29 +0530 Subject: [PATCH 020/119] adding the indLb rule which seems to work for now. --- .../extensions/modelcounts/UMCLanguage.scala | 169 +++++++++++++++++- .../extensions/modelcounts/UMCParser.scala | 15 +- .../extensions/modelcounts/UMCRewriter.scala | 67 ++++++- test/modelcounter/counting_2n.ucl | 35 ++-- 4 files changed, 259 insertions(+), 27 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index af0b17ef5..fad82374a 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -93,6 +93,9 @@ case class AssertStmt(e : l.Expr) extends Statement { } case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + assert (e1.xs == e2.xs && e2.xs == e3.xs) + assert (e1.ys == e2.ys && e2.ys == e3.ys) + override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) @@ -188,6 +191,53 @@ case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { } } +case class ConstEqStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130005 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constEq: " + e.toString() + " >= " + v.toString()) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } +} + +case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys(0)._2.isInt) + assert (fp.ys == f.ys && f.ys == g.ys) + + val n = fp.ys(0)._1 + + override val hashId = 130006 + override val md5hashCode = computeMD5Hash(fp, f, g, skolems) + override def toLines = { + List("assert indLB: " + fp.toString + " >= " + + f.toString() + " * " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + } + override val countingOps = Seq(fp, f, g) + override val expressions = Seq(fp, f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val e_fp = rewriter(fp.e) + val e_f = rewriter(f.e) + val e_g = rewriter(g.e) + val skolemsP = skolems.map(rewriter(_)).flatten + (e_fp, e_f, e_g) match { + case (Some(e1), Some(e2), Some(e3)) => + val fpNew = CountingOp(fp.xs, fp.ys, e1) + val fNew = CountingOp(f.xs, f.ys, e2) + val gNew = CountingOp(g.xs, g.ys, e3) + Some(IndLbStmt(fpNew, fNew, gNew, skolemsP)) + case _ => None + } + } +} case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + @@ -225,14 +275,27 @@ object UMCExpressions { l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) } + def andL(es : Seq[l.Expr]) = { + assert (es.size >= 1) + es.foldLeft(l.BoolLit(true).asInstanceOf[l.Expr])((acc, e) => and(acc, e)) + } + def or(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) } + + def orL(es : Seq[l.Expr]) = { + assert (es.size >= 1) + es.foldLeft(l.BoolLit(false).asInstanceOf[l.Expr])((acc, e) => or(acc, e)) + } def iff(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IffOp(), List(e1, e2)) } + def implies(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ImplicationOp(), List(e1, e2)) + } def not(e : l.Expr) = { l.OperatorApplication(l.NegationOp(), List(e)) } @@ -249,6 +312,10 @@ object UMCExpressions { l.OperatorApplication(l.SubOp(), List(e1, e2)) } + def mul(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.MulOp(), List(e1, e2)) + } + def le(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IntLEOp(), List(e1, e2)) } @@ -278,13 +345,17 @@ object UMCExpressions { ite(lt(e1, e2), e2, e1) } - def distinct(es : List[l.Expr]) = { + def distinct(es : l.Expr*) = { if (es.size <= 1) { l.BoolLit(true) } else { - l.OperatorApplication(l.DistinctOp(), es) + l.OperatorApplication(l.DistinctOp(), es.toList) } } + + def apply(id : l.Identifier, args : List[l.Expr]) = { + l.FuncApplication(id, args) + } def _findSubExpressions(e : l.Expr, fn : l.Expr => Boolean) : Set[l.Expr] = findSubExpressions((e, fn)) @@ -300,7 +371,15 @@ object UMCExpressions { case l.ConstArray(e, t) => _findSubExpressions(e, fn) case l.Tuple(es) => es.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) case l.OperatorApplication(op, args) => - args.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + val opExprs = op match { + case l.ArraySelect(inds) => + inds.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.ArrayUpdate(inds, value) => + inds.foldLeft(_findSubExpressions(value, fn))((acc, e) => _findSubExpressions(e, fn) ++ acc) + case _ => + Set.empty[l.Expr] + } + args.foldLeft(opExprs)((acc, e) => _findSubExpressions(e, fn) ++ acc) case l.Lambda(ids, e) => _findSubExpressions(e, fn) case l.FuncApplication(e1, e2) => e2.foldLeft(_findSubExpressions(e1, fn))((acc, e) => acc ++ _findSubExpressions(e, fn)) case CountingOp(xs, ys, e) => _findSubExpressions(e, fn) @@ -309,9 +388,52 @@ object UMCExpressions { else { subExprs } } ) + + def findSupport(es : Seq[l.Expr]) : Set[l.Identifier] = { + es.foldLeft(Set.empty[l.Identifier])((acc, e) => acc ++ findSupport(e)) + } + val findSupport : Memo[l.Expr, Set[l.Identifier]] = new Memo[l.Expr, Set[l.Identifier]]( + e => { + e match { + case _ : l.Literal | _ : l.Identifier => + Set.empty + case l.ConstArray(e, t) => + findSupport(e) + case l.Tuple(es) => + findSupport(es) + case l.OperatorApplication(op, args) => + val subSupport = findSupport(args) + op match { + case qOp : l.QuantifiedBooleanOperator => + subSupport -- qOp.variables.map(_._1).toSet + case l.ArraySelect(inds) => + findSupport(inds) + case l.ArrayUpdate(inds, value) => + findSupport(inds) ++ findSupport(value) ++ subSupport + case _ => + subSupport + } + case l.Lambda(ids, exp) => + val subSupport = findSupport(exp) + subSupport -- ids.map(_._1).toSet + case CountingOp(xs, ys, e) => + // FIXME: this is a major hack. + // Introducing this because we *want* the variables in a CountingOp + // to be "captured" by outer quantifiers. + // Need to revist and fix this. + Set.empty + case _ : l.ExternalIdentifier => + throw new Utils.AssertionError("Eliminate external identifiers before calling findSupport.") + case l.FuncApplication(e1, e2s) => + throw new Utils.AssertionError("Eliminate function applications before calling findSupport.") + } + } + ) } -class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { +class ExprRewriter(val rwMap : Map[l.Expr, l.Expr]) { + val supports = rwMap.map(p => p._1 -> UMCExpressions.findSupport(p._1)).toMap + val rewrite : Memo[l.Expr, l.Expr] = new Memo[l.Expr, l.Expr]( exp => { val expP : l.Expr = exp match { @@ -324,10 +446,43 @@ class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { val esP = es.map(e => rewrite(e)) l.Tuple(esP) case l.OperatorApplication(op, args) => - val argsP = args.map(arg => rewrite(arg)) - l.OperatorApplication(op, argsP) + op match { + case qOp : l.QuantifiedBooleanOperator => + val mapP = rwMap.filter(p => !qOp.variables.exists(v => supports(p._1).contains(v._1))) + if (mapP != rwMap) { + // have to eliminate the bound variables. + val rewriter = new ExprRewriter(mapP) + val argsP = args.map(arg => rewriter.rewrite(arg)) + l.OperatorApplication(op, argsP) + } else { + // do the usual + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + } + case l.ArraySelect(inds) => + val indsP = inds.map(ind => rewrite(ind)) + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(l.ArraySelect(indsP), argsP) + case l.ArrayUpdate(inds, e) => + val indsP = inds.map(ind => rewrite(ind)) + val eP = rewrite(e) + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(l.ArrayUpdate(indsP, eP), argsP) + case _ => + // do the usual. + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + } case l.Lambda(ids, exp) => - val expP = rewrite(exp) + val mapP = rwMap.filter(p => !ids.exists(v => supports(p._1).contains(v._1))) + val expP = if (mapP != rwMap) { + // have to eliminate the bound variables. + val rewriter = new ExprRewriter(mapP) + rewriter.rewrite(exp) + } else { + // do the usual. + rewrite(exp) + } l.Lambda(ids, exp) case l.FuncApplication(e1, e2s) => val e1p = rewrite(e1) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1cbcdb48b..1abd4f7ae 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -51,8 +51,11 @@ class UMCParser extends l.UclidParser { lazy val KwDisjoint = "disjoint" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" + lazy val KwConstEq = "constEq" + lazy val KwIndLb = "indLB" + lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) @@ -108,6 +111,16 @@ class UMCParser extends l.UclidParser { ConstUbStmt(e, v) } } | + KwAssert ~ KwConstEq ~ ":" ~> CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstEqStmt(e, v) + } + } | + KwAssert ~ KwIndLb ~ ":" ~> CountingExpr ~ (">=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ e3 ~ es => { + IndLbStmt(e1, e2, e3, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 98804d3eb..4460d4278 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -177,7 +177,7 @@ class UMCRewriter(cntProof : CountingProof) { val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] - val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) val query = E.and(conjunction, distincts) val assertStmt = l.AssertStmt(query, None, decorators) BlockStmt(blkDecls, List(assertStmt)) @@ -198,8 +198,65 @@ class UMCRewriter(cntProof : CountingProof) { List(blkStmt, assumeStmt) } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { + // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) + val f = indlb.f + val g = indlb.g + val f_xn = f.e + val g_yn = g.e + val ante = E.and(f_xn, g_yn) + val nplus1 = E.plus(indlb.n, l.IntLit(1)) + val skSubs = (f.xs.map(_._1.asInstanceOf[l.Expr]) zip indlb.skolems).toMap + + (indlb.n.asInstanceOf[l.Expr] -> nplus1) + val conseq = new ExprRewriter(skSubs).rewrite(f_xn) + val impl = E.implies(ante, conseq) + val qVars = f.xs ++ g.xs ++ f.ys + val qOp = E.forall(qVars, impl) + val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + + // Now we want to show injectivity of the skolem: + // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) + // ==> skolem(x1, y1, n) != skolem(x2, y2, n) + val x1s = f.xs.map(p => generateId(p._1.toString())) + val x2s = f.xs.map(p => generateId(p._1.toString())) + val y1s = g.xs.map(p => generateId(p._1.toString())) + val y2s = g.xs.map(p => generateId(p._1.toString())) + val rwx1 = new ExprRewriter((f.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwy1 = new ExprRewriter((g.xs zip y1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((f.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwy2 = new ExprRewriter((g.xs zip y2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwxy1 = new ExprRewriter(rwx1.rwMap ++ rwy1.rwMap) + val rwxy2 = new ExprRewriter(rwx2.rwMap ++ rwy2.rwMap) + val f_x1n = rwx1.rewrite(f.e) + val g_y1n = rwy1.rewrite(g.e) + val f_x2n = rwx2.rewrite(f.e) + val g_y2n = rwy2.rewrite(g.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + val ydiff = E.orL((y1s zip y2s).map(p => E.distinct(p._1, p._2))) + val sk1s = indlb.skolems.map(sk => rwxy1.rewrite(sk)) + val sk2s = indlb.skolems.map(sk => rwxy2.rewrite(sk)) + val ante2 = E.andL(List(f_x1n, f_x2n, g_y1n, g_y2n, E.or(xdiff, ydiff))) + val skdiff = E.orL((sk1s zip sk2s).map(p => E.distinct(p._1, p._2))) + val impl2 = E.implies(ante2, skdiff) + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ + (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ + (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + + // Finally, we have to produce the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) + val assumpQVars = f.xs ++ g.xs ++ f.ys + val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) + val assumpStmt = l.AssumeStmt(E.forall(assumpQVars, geqExpr), None) + + List(liftAssertStmt, injAssertStmt, assumpStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { - st match { + val newStmts : List[l.Statement] = st match { case a : AssertStmt => rewriteAssert(ufmap, a) case d : DisjointStmt => @@ -210,9 +267,15 @@ class UMCRewriter(cntProof : CountingProof) { rewriteConstLb(ufmap, lb) case ub : ConstUbStmt => rewriteConstUb(ufmap, ub) + case eq : ConstEqStmt => + rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v)) ++ + rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1))) + case indLb : IndLbStmt => + rewriteIndLb(ufmap, indLb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } + l.ASTNode.introducePos(true, true, newStmts, st.position) } lazy val controlBlock : List[l.GenericProofCommand] = List( diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 96067a266..eefce1a96 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -1,33 +1,34 @@ module main { + // U is the function V_f in the paper. define U(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + // V is the function V in the paper. define V(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + // W is the function V_1 in the paper. define W(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: !arr[i]); - define X(i : integer) : boolean = - (0 <= i < 2); + (n > 0) && (forall (i : integer) :: !arr[i]); + define X(b : boolean) : boolean = true; proof { + // The satisfying assignments to U are the disjoint union of the satisfying + // assignments to V and W. assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); - // #arr: W(arr, n) == 1 - assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; - assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; + // prove #arr: W(arr, n) == 1 + assert constEq: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; - // prove #i: X(i) == 2 - assert constLB: #[(i : integer) for (n : integer)] :: (X(i)) >= 2; - assert constUB: #[(i : integer) for (n : integer)] :: (X(i)) < 3; - assert forall (i : integer, n : integer) :: (#[(i : integer) for (n : integer)] :: (X(i))) == 2; - /* + // prove #i: X(b) == 2 + assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; + assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; + // prove the inductive step in the count. assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * - #[(i : integer) for (n : integer)] :: (0 <= i < 2) - using arr[n -> i]; - */ + #[(b : boolean) for (n : integer)] :: (X(b)) + skolems (arr[n -> b]); } } From 607f6fa477abf7ca22d99daeab2ce5107bdfb57b Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 20 Apr 2020 23:40:07 +0530 Subject: [PATCH 021/119] first attempt at model count proof. --- .../extensions/modelcounts/UMCLanguage.scala | 71 ++++++++++++------- .../extensions/modelcounts/UMCParser.scala | 23 ++++-- .../extensions/modelcounts/UMCRewriter.scala | 40 +++++------ test/debug/mc2.ucl | 24 +++++++ test/debug/mc3.ucl | 19 +++++ test/modelcounter/counting_2n.ucl | 12 ++-- 6 files changed, 136 insertions(+), 53 deletions(-) create mode 100644 test/debug/mc2.ucl create mode 100644 test/debug/mc3.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index fad82374a..006a765e5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -159,49 +159,67 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { } } } -case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstLbStmt(e : CountingOp, v : l.IntLit, assump: l.Expr) extends Statement { override val hashId = 130003 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constLB: " + e.toString() + " >= " + v.toString()) + } else { + List("assert constLB: " + assump.toString() + " ==> " + e.toString() + " >= " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit], aP)) case _ => None } } } -case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstUbStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends Statement { override val hashId = 130004 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constUB: " + e.toString() + " < " + v.toString()) + } else { + List("assert constUB: " + assump.toString() + " ==> " + e.toString() + " < " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit], aP)) case _ => None } } } -case class ConstEqStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends Statement { override val hashId = 130005 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constEq: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constEq: " + e.toString() + " >= " + v.toString()) + } else { + List("assert constEq: " + assump.toString() + " ==> " + e.toString() + " == " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit], assump)) case _ => None } } @@ -211,8 +229,8 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) assert (fp.ys(0)._2.isInt) assert (fp.ys == f.ys && f.ys == g.ys) - val n = fp.ys(0)._1 + assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) override val hashId = 130006 override val md5hashCode = computeMD5Hash(fp, f, g, skolems) @@ -262,11 +280,16 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S object UMCExpressions { // Helper functions to more easily construct expressions. def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - val op = l.ForallOp(vs, List.empty) - l.OperatorApplication(op, List(e)) + if (vs.size > 0) { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } else { + e + } } def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + assert (vs.size > 0) val op = l.ExistsOp(vs, List.empty) l.OperatorApplication(op, List(e)) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1abd4f7ae..aa0a0a8a7 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -58,7 +58,7 @@ class UMCParser extends l.UclidParser { lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = - positioned (TypeDecl | DefineDecl) + positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { @@ -103,17 +103,32 @@ class UMCParser extends l.UclidParser { } | KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstLbStmt(e, v) + ConstLbStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstLB ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstLbStmt(e, v, assump) } } | KwAssert ~ KwConstUB ~ ":" ~> CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstUbStmt(e, v) + ConstUbStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstUB ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstUbStmt(e, v, assump) } } | KwAssert ~ KwConstEq ~ ":" ~> CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstEqStmt(e, v) + ConstEqStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstEq ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstEqStmt(e, v, assump) } } | KwAssert ~ KwIndLb ~ ":" ~> CountingExpr ~ (">=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 4460d4278..2a0cf47a1 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -120,10 +120,6 @@ class UMCRewriter(cntProof : CountingProof) { }.flatten.toList } - def extractCountingArgs(e : CountingOp) = { - e.xs ++ e.ys - } - def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } @@ -139,7 +135,7 @@ class UMCRewriter(cntProof : CountingProof) { val o1 = st.e1 val o2 = st.e2 val o3 = st.e3 - val args = extractCountingArgs(o1) + val args = o1.xs ++ o1.ys val f1 = o1.e val f2 = o2.e val f3 = o3.e @@ -149,23 +145,24 @@ class UMCRewriter(cntProof : CountingProof) { val ufn1 = _apply(ufMap(o1)) val ufn2 = _apply(ufMap(o2)) val ufn3 = _apply(ufMap(o3)) - val assumeExpr = E.forall(args, E.eq(ufn1, E.plus(ufn2, ufn3))) + val assumeExpr = E.forall(st.e1.ys, E.eq(ufn1, E.plus(ufn2, ufn3))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(assertStmt, assumeStmt) } def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.op) val ufn = _apply(ufMap(st.op)) - val assumeExpr = E.forall(args, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) + val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) - val assertExpr = E.forall(args, E.eq(ufn, st.cnt)) + val assertExpr = E.forall(st.op.ys, E.eq(ufn, st.cnt)) val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } - def getCnstBoundStmt(ufMap : UFMap, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { + def getCnstBoundStmt(ufMap : UFMap, assump : l.Expr, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { val cntArgs = e.xs + val qVars = e.ys + val ante = assump val argVars = cntArgs.map(a => a._1) val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) @@ -178,22 +175,22 @@ class UMCRewriter(cntProof : CountingProof) { val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) - val query = E.and(conjunction, distincts) + val query = E.forall(qVars, E.implies(ante, E.and(conjunction, distincts))) val assertStmt = l.AssertStmt(query, None, decorators) BlockStmt(blkDecls, List(assertStmt)) } def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { - val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) + val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(extractCountingArgs(st.e), E.ge(ufn, st.v)) + val assumeExpr = E.forall(st.e.ys, E.implies(st.assump, E.ge(ufn, st.v))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { - val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator)) + val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(extractCountingArgs(st.e), E.lt(ufn, st.v)) + val assumeExpr = E.forall(st.e.ys, E.implies(st.assump, E.lt(ufn, st.v))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } @@ -248,11 +245,14 @@ class UMCRewriter(cntProof : CountingProof) { val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) - val assumpQVars = f.xs ++ g.xs ++ f.ys val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) - val assumpStmt = l.AssumeStmt(E.forall(assumpQVars, geqExpr), None) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) + + val ufpn = _apply(ufMap(indlb.fp)) + val eqExpr = E.eq(ufnplus1, ufpn) + val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - List(liftAssertStmt, injAssertStmt, assumpStmt) + List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { @@ -268,8 +268,8 @@ class UMCRewriter(cntProof : CountingProof) { case ub : ConstUbStmt => rewriteConstUb(ufmap, ub) case eq : ConstEqStmt => - rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v)) ++ - rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1))) + rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v, eq.assump)) ++ + rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1), eq.assump)) case indLb : IndLbStmt => rewriteIndLb(ufmap, indLb) case _ => diff --git a/test/debug/mc2.ucl b/test/debug/mc2.ucl new file mode 100644 index 000000000..919c8d01e --- /dev/null +++ b/test/debug/mc2.ucl @@ -0,0 +1,24 @@ + module main { + define X (b: boolean): boolean = true; + function count_1(n: integer): integer; // line 0 + axiom assump_1 : (forall (n : integer) :: (count_1(n) >= 0)); // + function count_2(n: integer): integer; // line 0 + axiom assump_2 : (forall (n : integer) :: (count_2(n) >= 0)); // + + procedure countingProof() returns () + { + var arr : [integer]boolean; // line 22 + var n : integer; // line 22 + assert [cover, SATOnly]: + forall (n : integer) :: + (n > 0 && + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i])); + } + control { + v = verify(countingProof /* countingProof*/); // line 0 + check; // line 0 + print_results; // line 0 + } + } + diff --git a/test/debug/mc3.ucl b/test/debug/mc3.ucl new file mode 100644 index 000000000..3ad825520 --- /dev/null +++ b/test/debug/mc3.ucl @@ -0,0 +1,19 @@ + +module main{ + init { + var arr_2 : [integer]boolean; // line 16 + var arr_3 : [integer]boolean; // line 16 + var n : integer; // line 16 + assert[cover]: + (((true && (forall (i : integer) :: !((arr_2)[i]))) && + (forall (i : integer) :: !((arr_3)[i]))) && + distinct({arr_2}, {arr_3})); // line 0 + + } + + control { + v = unroll(1); + check; + print_results; + } +} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index eefce1a96..867527c22 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -2,14 +2,14 @@ module main { // U is the function V_f in the paper. define U(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); // V is the function V in the paper. define V(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && (exists (i : integer) :: 0 <= i < n && arr[i]); // W is the function V_1 in the paper. define W(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: !arr[i]); + (n >= 0) && (forall (i : integer) :: !arr[i]); define X(b : boolean) : boolean = true; proof { @@ -19,12 +19,14 @@ module main { #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); // prove #arr: W(arr, n) == 1 - assert constEq: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; - assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + assert constEq: (n >= 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; + // prove #arr: U(arr, 1) == 1 + assert constEq: (n == 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == 1; // prove #i: X(b) == 2 assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; // prove the inductive step in the count. + // #arr. U(arr, n + 1) >= #arr. U(arr, n) * 2 assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * #[(b : boolean) for (n : integer)] :: (X(b)) From 5101ae0c940f465eef32e9d864433a3e4f66875c Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 22 Apr 2020 21:17:41 +0530 Subject: [PATCH 022/119] Compilation fix after merge. --- src/main/scala/uclid/lang/Scope.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/lang/Scope.scala b/src/main/scala/uclid/lang/Scope.scala index 91b1b9a0e..94f394537 100644 --- a/src/main/scala/uclid/lang/Scope.scala +++ b/src/main/scala/uclid/lang/Scope.scala @@ -256,7 +256,7 @@ case class Scope ( case SharedVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.SharedVar(id, typ))) case ConstantLitDecl(id, lit) => Scope.addToMap(map, Scope.ConstantLit(id, lit)) case ConstantsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.ConstantVar(id, typ))) - case GrammarDecl(id, sig, _) => Scope.addToMap(map, Scope.Grammar(id, sig.typ)) + case GrammarDecl(id, sig, nts) => Scope.addToMap(map, Scope.Grammar(id, sig.typ, nts)) case FunctionDecl(id, sig) => Scope.addToMap(map, Scope.Function(id, sig.typ)) case SynthesisFunctionDecl(id, sig, _, _, _) => Scope.addToMap(map, Scope.Function(id, sig.typ)) // FIXME case DefineDecl(id, sig, expr) => Scope.addToMap(map, Scope.Define(id, sig.typ, DefineDecl(id, sig, expr))) From df5b16278d0d614aba047627fc20dd5a5e2c8766 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 8 May 2020 13:19:53 +0530 Subject: [PATCH 023/119] fixed disjoint to be or rule --- .../extensions/modelcounts/UMCLanguage.scala | 14 +++--- .../extensions/modelcounts/UMCMain.scala | 1 + .../extensions/modelcounts/UMCParser.scala | 14 +++--- .../extensions/modelcounts/UMCRewriter.scala | 50 +++++++++---------- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 006a765e5..a565ac561 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -81,7 +81,7 @@ case class AssertStmt(e : l.Expr) extends Statement { case _ => false } } - UMCExpressions.findSubExpressions(e, isCountingOp _).map(_.asInstanceOf[CountingOp]).toSeq + UMCExpressions.findSubExpressions(e, isCountingOp).map(_.asInstanceOf[CountingOp]).toSeq } override val expressions = Seq(e) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { @@ -92,13 +92,13 @@ case class AssertStmt(e : l.Expr) extends Statement { } } -case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { +case class OrStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { assert (e1.xs == e2.xs && e2.xs == e3.xs) assert (e1.ys == e2.ys && e2.ys == e3.ys) override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) - override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def toLines = List("assert or: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) override val countingOps = Seq(e1, e2, e3) override val expressions = Seq(e1, e2, e3) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { @@ -107,7 +107,7 @@ case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) exten val op1 = CountingOp(e1.xs, e1.ys, e1p) val op2 = CountingOp(e2.xs, e2.ys, e2p) val op3 = CountingOp(e3.xs, e3.ys, e3p) - Some(DisjointStmt(op1, op2, op3)) + Some(OrStmt(op1, op2, op3)) case _ => None } } @@ -118,8 +118,7 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { op.e match { case l.OperatorApplication(l.ConjunctionOp(), List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), - l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => - lb + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => lb case _ => throw new Utils.AssertionError("Unexpected operand to range expression.") } @@ -128,8 +127,7 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { op.e match { case l.OperatorApplication(l.ConjunctionOp(), List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), - l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => - ub + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => ub case _ => throw new Utils.AssertionError("Unexpected operand to range expression.") } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 87661f2a6..45232ec69 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -61,6 +61,7 @@ object UMCMain { println("Parsed module: " + module.id.toString()) println(module.toString()) val moduleP = new UMCRewriter(module).process() + println("\nModule after rewriting: ") println(moduleP.toString()) runProcessedModel(moduleP) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index aa0a0a8a7..d51b00a81 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -48,19 +48,19 @@ import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" - lazy val KwDisjoint = "disjoint" + lazy val KwOr = "or" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" lazy val KwConstEq = "constEq" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - - lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) + + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) - lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = + lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { case xs ~ ys => (xs, ys) } | @@ -75,7 +75,7 @@ class UMCParser extends l.UclidParser { } lazy val C0: PackratParser[l.Expr] = positioned { CountingExpr | E1 } - + override lazy val E15: PackratParser[l.Expr] = positioned { Literal | "{" ~> Expr ~ rep("," ~> Expr) <~ "}" ^^ {case e ~ es => l.Tuple(e::es)} | @@ -92,9 +92,9 @@ class UMCParser extends l.UclidParser { lazy val CExpr: PackratParser[l.Expr] = positioned { C0 } lazy val Stmt: PackratParser[Statement] = positioned { - KwAssert ~ KwDisjoint ~ ":" ~> + KwAssert ~ KwOr ~ ":" ~> (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { - case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + case e1 ~ e2 ~ e3 => OrStmt(e1, e2, e3) } | KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { case e1 ~ e2 => { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2a0cf47a1..bbc2d90fa 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -60,7 +60,7 @@ class UMCRewriter(cntProof : CountingProof) { } /** Identifiers that are already declared in the module. */ - val existingIds = cntProof.decls.map(d => d.declNames).flatten.toSet + val existingIds = cntProof.decls.flatMap(d => d.declNames).toSet /** Identifiers that are declared + newly generated names. */ val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds /** Counters that track (roughly) the number of generated identifiers with each prefix. */ @@ -103,21 +103,21 @@ class UMCRewriter(cntProof : CountingProof) { l.OperatorApplication(l.IntGEOp(), List(e, l.IntLit(0))) } def quantify(ufDecl : l.FunctionDecl, e : l.Expr) : l.Expr = { - if (ufDecl.sig.args.size > 0) { + if (ufDecl.sig.args.nonEmpty) { l.OperatorApplication(l.ForallOp(ufDecl.sig.args, List.empty), List(e)) } else { e } } - ufMap.map { - p => { - val ufDecl = p._2 - val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) - val axExpr = quantify(ufDecl, innerExpr) - val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) - List(ufDecl, axiomDecl) - } - }.flatten.toList + ufMap.flatMap { + p => { + val ufDecl = p._2 + val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) + val axExpr = quantify(ufDecl, innerExpr) + val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) + List(ufDecl, axiomDecl) + } + }.toList } def _apply(uf : l.FunctionDecl) = { @@ -131,7 +131,7 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt) } - def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + def rewriteOr(ufMap : UFMap, st : OrStmt) : List[l.Statement] = { val o1 = st.e1 val o2 = st.e2 val o3 = st.e3 @@ -149,7 +149,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeStmt = l.AssumeStmt(assumeExpr, None) List(assertStmt, assumeStmt) } - + def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { val ufn = _apply(ufMap(st.op)) val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) @@ -158,7 +158,7 @@ class UMCRewriter(cntProof : CountingProof) { val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } - + def getCnstBoundStmt(ufMap : UFMap, assump : l.Expr, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { val cntArgs = e.xs val qVars = e.ys @@ -167,7 +167,7 @@ class UMCRewriter(cntProof : CountingProof) { val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(e.e, rwMap, l.Scope.empty)) - val newVars : List[(l.Identifier, l.Type)] = + val newVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (cntArgs zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten ++ e.ys val blkDecls = newVars.map(p => l.BlockVarsDecl(List(p._1), p._2)) @@ -186,7 +186,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } - + def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) @@ -210,10 +210,10 @@ class UMCRewriter(cntProof : CountingProof) { val qVars = f.xs ++ g.xs ++ f.ys val qOp = E.forall(qVars, impl) val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) - + // Now we want to show injectivity of the skolem: - // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) - // ==> skolem(x1, y1, n) != skolem(x2, y2, n) + // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) + // ==> skolem(x1, y1, n) != skolem(x2, y2, n) val x1s = f.xs.map(p => generateId(p._1.toString())) val x2s = f.xs.map(p => generateId(p._1.toString())) val y1s = g.xs.map(p => generateId(p._1.toString())) @@ -240,18 +240,18 @@ class UMCRewriter(cntProof : CountingProof) { (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) - + // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) - + val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - + List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } @@ -259,8 +259,8 @@ class UMCRewriter(cntProof : CountingProof) { val newStmts : List[l.Statement] = st match { case a : AssertStmt => rewriteAssert(ufmap, a) - case d : DisjointStmt => - rewriteDisjoint(ufmap, d) + case d : OrStmt => + rewriteOr(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) case lb : ConstLbStmt => @@ -298,7 +298,7 @@ class UMCRewriter(cntProof : CountingProof) { val countingOps = identifyCountOps(cntProof.stmts) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val newProofStmts = cntProof.stmts.map(st => rewriteAssert(ufMap, st)).flatten + val newProofStmts = cntProof.stmts.flatMap(st => rewriteAssert(ufMap, st)) val newProofProc = l.ProcedureDecl( l.Identifier("countingProof"), l.ProcedureSig(List.empty, List.empty), From 999b2c11b83e6f3882f6faae353f1c3bdec27085 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 8 May 2020 13:27:28 +0530 Subject: [PATCH 024/119] updated the test example --- test/modelcounter/counting_2n.ucl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 867527c22..d270863e8 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -15,7 +15,7 @@ module main { proof { // The satisfying assignments to U are the disjoint union of the satisfying // assignments to V and W. - assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == + assert or: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); // prove #arr: W(arr, n) == 1 From 94756559dea15fcc9015d37a6343ce9e58ef41c4 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 11:10:32 +0530 Subject: [PATCH 025/119] minor fix to example --- test/modelcounter/hello.ucl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index a8dfe8849..95c10e6b9 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -9,7 +9,7 @@ module main { assert constLB: #[(n:integer)] :: (g(n)) >= 5; assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; - assert disjoint: #[(n:integer) for ()] :: (f(n)) == + assert or: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } From 8fc244b801aa26c61a2d58262e718aa4ea4d7c34 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 11:12:01 +0530 Subject: [PATCH 026/119] added UB rule --- .../extensions/modelcounts/UMCLanguage.scala | 19 ++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 22 ++++++++++------ .../extensions/modelcounts/UMCRewriter.scala | 26 ++++++++++++++++--- test/modelcounter/test-UB.ucl | 9 +++++++ 4 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 test/modelcounter/test-UB.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index a565ac561..f61b1067e 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -254,6 +254,25 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } } + +case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { + override val hashId = 130007 + override val md5hashCode = computeMD5Hash(e1, e2) + override def toLines = + List("assert UB: " + e1.toString() + " <= " + e2.toString()) + override val countingOps = Seq(e1, e2) + override val expressions = Seq(e1, e2) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e)) match { + case (Some(e1p), Some(e2p)) => + val e1New = CountingOp(e1.xs, e1.ys, e1p) + val e2New = CountingOp(e2.xs, e2.ys, e2p) + Some(UbStmt(e1New, e2New)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index d51b00a81..4b9b4ad4d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -47,15 +47,16 @@ import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { - lazy val KwProof = "proof" - lazy val KwOr = "or" - lazy val KwConstLB = "constLB" - lazy val KwConstUB = "constUB" - lazy val KwConstEq = "constEq" - lazy val KwIndLb = "indLB" - lazy val KwSkolems = "skolems" + lazy val KwProof = "proof" + lazy val KwConstLB = "constLB" + lazy val KwConstUB = "constUB" + lazy val KwConstEq = "constEq" + lazy val KwUB = "UB" + lazy val KwOr = "or" + lazy val KwIndLb = "indLB" + lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -136,6 +137,11 @@ class UMCParser extends l.UclidParser { IndLbStmt(e1, e2, e3, es) } } | + KwAssert ~ KwUB ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) <~ ";" ^^ { + case e1 ~ e2 => { + UbStmt(e1, e2) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index bbc2d90fa..b29f430cd 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -195,6 +195,21 @@ class UMCRewriter(cntProof : CountingProof) { List(blkStmt, assumeStmt) } + def rewriteUb(ufMap : UFMap, st : UbStmt) : List[l.Statement] = { + val s1 = st.e1 + val s2 = st.e2 + val args = s1.xs ++ s1.ys + val f = s1.e + val g = s2.e + val assertExpr = E.forall(args, E.implies(f, g)) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, ufn2)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -255,6 +270,7 @@ class UMCRewriter(cntProof : CountingProof) { List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { val newStmts : List[l.Statement] = st match { case a : AssertStmt => @@ -263,15 +279,17 @@ class UMCRewriter(cntProof : CountingProof) { rewriteOr(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) - case lb : ConstLbStmt => - rewriteConstLb(ufmap, lb) - case ub : ConstUbStmt => - rewriteConstUb(ufmap, ub) + case constLb : ConstLbStmt => + rewriteConstLb(ufmap, constLb) + case constUb : ConstUbStmt => + rewriteConstUb(ufmap, constUb) case eq : ConstEqStmt => rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v, eq.assump)) ++ rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1), eq.assump)) case indLb : IndLbStmt => rewriteIndLb(ufmap, indLb) + case ub : UbStmt => + rewriteUb(ufmap, ub) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-UB.ucl b/test/modelcounter/test-UB.ucl new file mode 100644 index 000000000..4a0c822e3 --- /dev/null +++ b/test/modelcounter/test-UB.ucl @@ -0,0 +1,9 @@ +module main { + define f(n : integer) : boolean = 11 <= n < 21; + define g(n : integer) : boolean = 5 <= n < 21; + + proof { + assert UB: #[(n:integer)] :: (f(n)) <= #[(n:integer)] :: (g(n)); + } +} + From aedd7dd1934ad4a6aecbba1169ae9d338544fdc7 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 14:18:31 +0530 Subject: [PATCH 027/119] minor edit: adding assert to UB rule - ensuring counting quantifier is applied on the same set of variables in each expression --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index f61b1067e..1e0cc6186 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -256,6 +256,8 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { + assert (e1.xs == e2.xs && e1.ys == e2.ys) + override val hashId = 130007 override val md5hashCode = computeMD5Hash(e1, e2) override def toLines = From ec2a7567f6340bc7ba17944d3296926cbe83b1d1 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 17:44:19 +0530 Subject: [PATCH 028/119] added AndUB rule --- .../extensions/modelcounts/UMCLanguage.scala | 23 +++++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 7 +++++- .../extensions/modelcounts/UMCRewriter.scala | 22 ++++++++++++++++++ test/modelcounter/test-AndUB.ucl | 14 +++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 test/modelcounter/test-AndUB.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 1e0cc6186..643ee12d5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -42,6 +42,7 @@ import uclid.UclidMain import uclid.{lang => l} import uclid.Utils import uclid.Memo +import uclid.lang.{Identifier, Type} /** CountingOp is a new operator we introduce for the UMC extension. */ @@ -275,6 +276,28 @@ case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { } } +case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + println("Reached here") + assert (e1.xs.toSet == (e2.xs.toSet | e1.xs.toSet)) + + println("Assertions passed") + override val hashId = 130008 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert andUB: " + e1.toString() + " <= " + e2.toString() + " * " + e3.toString()) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(AndUbStmt(op1, op2, op3)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 4b9b4ad4d..28cbda46d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -52,11 +52,12 @@ class UMCParser extends l.UclidParser { lazy val KwConstUB = "constUB" lazy val KwConstEq = "constEq" lazy val KwUB = "UB" + lazy val KwAndUB = "andUB" lazy val KwOr = "or" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -142,6 +143,10 @@ class UMCParser extends l.UclidParser { UbStmt(e1, e2) } } | + KwAssert ~ KwAndUB ~ ":" ~> + (CountingExpr <~ "<=") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { + case e1 ~ e2 ~ e3 => AndUbStmt(e1, e2, e3) + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index b29f430cd..a9eeb6288 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -210,6 +210,26 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteAndUb(ufMap : UFMap, st : AndUbStmt) : List[l.Statement] = { + val s1 = st.e1 + val s2 = st.e2 + val s3 = st.e3 + val args1 = s1.xs ++ s1.ys + val args2 = s2.xs ++ s2.ys + val args3 = s3.xs ++ s3.ys + val f1 = s1.e + val f2 = s2.e + val f3 = s3.e + val assertExpr = E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val ufn3 = _apply(ufMap(s3)) + val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, E.mul(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -290,6 +310,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteIndLb(ufmap, indLb) case ub : UbStmt => rewriteUb(ufmap, ub) + case andUb : AndUbStmt => + rewriteAndUb(ufmap, andUb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-AndUB.ucl b/test/modelcounter/test-AndUB.ucl new file mode 100644 index 000000000..0c1e08038 --- /dev/null +++ b/test/modelcounter/test-AndUB.ucl @@ -0,0 +1,14 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 5 <= n < 10; + define h(n : integer, m : integer) : boolean = (1 <= n < 10) && (5 <= m < 10); + + proof { + assert andUB: #[(n: integer, m : integer)] :: (h(n, m)) <= + #[(n: integer)] :: (f(n)) * + #[(m: integer)] :: (g(m)); + } +} + + + From dff3427ecc089fdbee22d1efa67872b6b57983d2 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 10 May 2020 16:50:30 +0530 Subject: [PATCH 029/119] added Disjoint rule --- .../extensions/modelcounts/UMCLanguage.scala | 24 ++++++++++++++--- .../extensions/modelcounts/UMCParser.scala | 8 +++++- .../extensions/modelcounts/UMCRewriter.scala | 26 +++++++++++++++++++ test/modelcounter/test-disjoint.ucl | 11 ++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 test/modelcounter/test-disjoint.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 643ee12d5..0ee4aecd2 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -277,10 +277,8 @@ case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { } case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { - println("Reached here") - assert (e1.xs.toSet == (e2.xs.toSet | e1.xs.toSet)) + assert (e1.xs.toSet == (e2.xs.toSet union e3.xs.toSet)) - println("Assertions passed") override val hashId = 130008 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert andUB: " + e1.toString() + " <= " + e2.toString() + " * " + e3.toString()) @@ -298,6 +296,26 @@ case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends } } +case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + assert (e1.xs.toSet == (e2.xs.toSet union e3.xs.toSet)) + + override val hashId = 130009 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " * " + e3.toString()) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(DisjointStmt(op1, op2, op3)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 28cbda46d..ef9ac50d6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -53,11 +53,13 @@ class UMCParser extends l.UclidParser { lazy val KwConstEq = "constEq" lazy val KwUB = "UB" lazy val KwAndUB = "andUB" + lazy val KwDisjoint = "disjoint" lazy val KwOr = "or" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, + KwDisjoint) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -147,6 +149,10 @@ class UMCParser extends l.UclidParser { (CountingExpr <~ "<=") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => AndUbStmt(e1, e2, e3) } | + KwAssert ~ KwDisjoint ~ ":" ~> + (CountingExpr <~ "==") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { + case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index a9eeb6288..20c962e31 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -230,6 +230,30 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + val e2s = st.e2.xs.toSet + val e3s = st.e3.xs.toSet + val intersection = if ((e2s intersect e3s).isEmpty) l.BoolLit(true) + else l.BoolLit(false) + val s1 = st.e1 + val s2 = st.e2 + val s3 = st.e3 + val args1 = s1.xs ++ s1.ys + val args2 = s2.xs ++ s2.ys + val args3 = s3.xs ++ s3.ys + val f1 = s1.e + val f2 = s2.e + val f3 = s3.e + val assertExpr = E.and(E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))), intersection) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val ufn3 = _apply(ufMap(s3)) + val assumeExpr = E.forall(st.e1.ys, E.eq(ufn1, E.mul(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -312,6 +336,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteUb(ufmap, ub) case andUb : AndUbStmt => rewriteAndUb(ufmap, andUb) + case disj : DisjointStmt => + rewriteDisjoint(ufmap, disj) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-disjoint.ucl b/test/modelcounter/test-disjoint.ucl new file mode 100644 index 000000000..5df6c3990 --- /dev/null +++ b/test/modelcounter/test-disjoint.ucl @@ -0,0 +1,11 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 5 <= n < 10; + define h(n : integer, m : integer) : boolean = (1 <= n < 10) && (5 <= m < 10); + + proof { + assert disjoint: #[(n: integer, m : integer)] :: (h(n, m)) == + #[(n: integer)] :: (f(n)) * + #[(m: integer)] :: (g(m)); + } +} \ No newline at end of file From ef7a8031a7455da25e2dd448045fc77e3ac6a0a5 Mon Sep 17 00:00:00 2001 From: ssahai Date: Thu, 14 May 2020 13:03:36 +0530 Subject: [PATCH 030/119] added injectivity rule --- .../extensions/modelcounts/UMCLanguage.scala | 22 +++++++++- .../extensions/modelcounts/UMCParser.scala | 8 +++- .../extensions/modelcounts/UMCRewriter.scala | 44 +++++++++++++++++++ test/modelcounter/test-injectivity.ucl | 8 ++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 test/modelcounter/test-injectivity.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 0ee4aecd2..5cad4dfed 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -244,7 +244,7 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : val e_fp = rewriter(fp.e) val e_f = rewriter(f.e) val e_g = rewriter(g.e) - val skolemsP = skolems.map(rewriter(_)).flatten + val skolemsP = skolems.flatMap(rewriter(_)) (e_fp, e_f, e_g) match { case (Some(e1), Some(e2), Some(e3)) => val fpNew = CountingOp(fp.xs, fp.ys, e1) @@ -316,6 +316,26 @@ case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) exten } } +case class InjectivityStmt(f : CountingOp, g: CountingOp, skolems : List[l.Expr] ) extends Statement { + override val hashId = 130010 + override val md5hashCode = computeMD5Hash(f, g, skolems) + override def toLines = + List("assert injectivity: " + f.toString() + " <= " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + override val countingOps = Seq(f, g) + override val expressions = Seq(f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val skolemsP = skolems.flatMap(rewriter(_)) + (rewriter(f.e), rewriter(g.e)) match { + case (Some(e1p), Some(e2p)) => + val e1New = CountingOp(f.xs, f.ys, e1p) + val e2New = CountingOp(g.xs, g.ys, e2p) + Some(InjectivityStmt(e1New, e2New, skolemsP)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index ef9ac50d6..a7e889d67 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -55,11 +55,12 @@ class UMCParser extends l.UclidParser { lazy val KwAndUB = "andUB" lazy val KwDisjoint = "disjoint" lazy val KwOr = "or" + lazy val KwInj = "injectivity" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint) + KwDisjoint, KwInj) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -153,6 +154,11 @@ class UMCParser extends l.UclidParser { (CountingExpr <~ "==") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) } | + KwAssert ~ KwInj ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ es => { + InjectivityStmt(e1, e2, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 20c962e31..97e703dd3 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -230,6 +230,48 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteInjectivity(ufMap : UFMap, inj : InjectivityStmt) : List[l.Statement] = { + // forall x. f(x) ==> g(skolem(x)) + val f = inj.f + val g = inj.g + val f_x = f.e + val g_y = g.e + val skSubs = (g.xs.map(_._1.asInstanceOf[l.Expr]) zip inj.skolems).toMap + val conseq = new ExprRewriter(skSubs).rewrite(g_y) + val impl = E.implies(f_x, conseq) + val qVars = f.xs ++ f.ys + val qOp = E.forall(qVars, impl) + val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + + // Next we want to show injectivity of the skolem: + // f(x1) && f(x2) && (x1 != x2) ==> skolem(x1) != skolem(x2) + val x1s = f.xs.map(p => generateId(p._1.toString())) + val x2s = f.xs.map(p => generateId(p._1.toString())) + val rwx1 = new ExprRewriter((f.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((f.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + + val f_x1n = rwx1.rewrite(f.e) + val f_x2n = rwx2.rewrite(f.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + val sk1s = inj.skolems.map(sk => rwx1.rewrite(sk)) + val sk2s = inj.skolems.map(sk => rwx2.rewrite(sk)) + val ante2 = E.andL(List(f_x1n, f_x2n, xdiff)) + val skdiff = E.orL((sk1s zip sk2s).map(p => E.distinct(p._1, p._2))) + val impl2 = E.implies(ante2, skdiff) + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + + // Now the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val leqExpr = E.le(ufn, ugn) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, leqExpr), None) + + List(liftAssertStmt, injAssertStmt, assumpStmt1) + } + + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { val e2s = st.e2.xs.toSet val e3s = st.e3.xs.toSet @@ -338,6 +380,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteAndUb(ufmap, andUb) case disj : DisjointStmt => rewriteDisjoint(ufmap, disj) + case inj : InjectivityStmt => + rewriteInjectivity(ufmap, inj) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-injectivity.ucl b/test/modelcounter/test-injectivity.ucl new file mode 100644 index 000000000..349f09ae4 --- /dev/null +++ b/test/modelcounter/test-injectivity.ucl @@ -0,0 +1,8 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 15 <= n < 30; + + proof { + assert injectivity: #[(n: integer)] :: (f(n)) <= #[(m: integer)] :: (g(m)) skolems(n+14); + } +} \ No newline at end of file From 38107043eacd0f1717a250857c6410255e1a65eb Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 15:31:49 +0530 Subject: [PATCH 031/119] zkhat example --- test/modelcounter/counting-zkhat.ucl | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/modelcounter/counting-zkhat.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl new file mode 100644 index 000000000..526aba2da --- /dev/null +++ b/test/modelcounter/counting-zkhat.ucl @@ -0,0 +1,46 @@ +// Model counting in ZK Hats - Motivating example in CAV 2020 paper + +module main { + + define V(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define Vf(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define V1(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: !Y[i]); + + define W(i : boolean) : boolean = 0 <= i < 2; + + proof { + // Proof rules 2 - 7 from paper. + + // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) + assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == + #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + + #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); + + // 3. (ConstEq) #Y. V1(Y, R) = 1 + assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; + + // 4. (ConstEq) #Y. Vf(Y, 1) = 2 + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + + // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) + // TODO + + // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) + // TODO : Not Working yet + + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R-1)) * + #[(i: integer)] :: (W(i)) + skolems(Y[(R+1) -> i]); + + // 7. (Range) #i. W(i) == 2 + assert range: #[(i: integer)] :: (W(i)) == 2; + + } +} From 542183c41971b4a0a0a6c6d8727ed78a2326fae9 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 16:20:55 +0530 Subject: [PATCH 032/119] parsing indUB. Rewriting is pending for now. --- .../extensions/modelcounts/UMCLanguage.scala | 32 +++++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 8 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 5cad4dfed..a443208a9 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -336,6 +336,38 @@ case class InjectivityStmt(f : CountingOp, g: CountingOp, skolems : List[l.Expr] } } +case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys(0)._2.isInt) + assert (fp.ys == f.ys && f.ys == g.ys) + val n = fp.ys(0)._1 + assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) + + override val hashId = 130011 + override val md5hashCode = computeMD5Hash(fp, f, g, skolems) + override def toLines = { + List("assert indUB: " + fp.toString + " <= " + + f.toString() + " * " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + } + override val countingOps = Seq(fp, f, g) + override val expressions = Seq(fp, f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val e_fp = rewriter(fp.e) + val e_f = rewriter(f.e) + val e_g = rewriter(g.e) + val skolemsP = skolems.flatMap(rewriter(_)) + (e_fp, e_f, e_g) match { + case (Some(e1), Some(e2), Some(e3)) => + val fpNew = CountingOp(fp.xs, fp.ys, e1) + val fNew = CountingOp(f.xs, f.ys, e2) + val gNew = CountingOp(g.xs, g.ys, e3) + Some(IndUbStmt(fpNew, fNew, gNew, skolemsP)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index a7e889d67..877076055 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -57,10 +57,11 @@ class UMCParser extends l.UclidParser { lazy val KwOr = "or" lazy val KwInj = "injectivity" lazy val KwIndLb = "indLB" + lazy val KwIndUb = "indUB" lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint, KwInj) + KwDisjoint, KwInj, KwIndUb) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -159,6 +160,11 @@ class UMCParser extends l.UclidParser { InjectivityStmt(e1, e2, es) } } | + KwAssert ~ KwIndUb ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ e3 ~ es => { + IndUbStmt(e1, e2, e3, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } From b59c15b362f3d3b7c4a6614b2f63d18fd35f0cfb Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 19:16:38 +0530 Subject: [PATCH 033/119] added support for lemmas via lemma block - parsing successfully --- .../extensions/modelcounts/UMCLanguage.scala | 19 ++++--- .../extensions/modelcounts/UMCParser.scala | 56 +++++++++++++++++-- .../extensions/modelcounts/UMCRewriter.scala | 3 +- test/modelcounter/test-lemma.ucl | 20 +++++++ 4 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 test/modelcounter/test-lemma.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index a443208a9..f0403fc4c 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -368,10 +368,12 @@ case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } -case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { +case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.Decl], + stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + - Utils.join(decls.map(" " + _.toString()), "\n") + + Utils.join(decls.map(" " + _.toString()), "\n") + "\n\n" + + Utils.join(lemmas.map(" " + _.toString()), "\n") + "\n\n proof {\n" + Utils.join(stmts.map(st => " " + st.toString()), "\n") + "\n }\n}" @@ -381,8 +383,9 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S def rewriterFn(expr : l.Expr) : Option[l.Expr] = { rewriter.visitExpr(expr, ctx) } - val stmtsP = stmts.map(st => st.rewrite(rewriterFn)).flatten - CountingProof(id, decls, stmtsP) + + val stmtsP = stmts.flatMap(st => st.rewrite(rewriterFn)) + CountingProof(id, decls, lemmas, stmtsP) } override val hashId = 131001 override val md5hashCode = computeMD5Hash(id, decls, stmts) @@ -392,7 +395,7 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S object UMCExpressions { // Helper functions to more easily construct expressions. def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - if (vs.size > 0) { + if (vs.nonEmpty) { val op = l.ForallOp(vs, List.empty) l.OperatorApplication(op, List(e)) } else { @@ -401,7 +404,7 @@ object UMCExpressions { } def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - assert (vs.size > 0) + assert (vs.nonEmpty) val op = l.ExistsOp(vs, List.empty) l.OperatorApplication(op, List(e)) } @@ -411,7 +414,7 @@ object UMCExpressions { } def andL(es : Seq[l.Expr]) = { - assert (es.size >= 1) + assert (es.nonEmpty) es.foldLeft(l.BoolLit(true).asInstanceOf[l.Expr])((acc, e) => and(acc, e)) } @@ -420,7 +423,7 @@ object UMCExpressions { } def orL(es : Seq[l.Expr]) = { - assert (es.size >= 1) + assert (es.nonEmpty) es.foldLeft(l.BoolLit(false).asInstanceOf[l.Expr])((acc, e) => or(acc, e)) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 877076055..630661146 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -39,14 +39,14 @@ package uclid.extensions.modelcounts -import uclid.{lang => l} -import uclid.Utils -import uclid.lang.Identifier -import uclid.lang.Type +import uclid.{Utils, lang => l} +import uclid.lang.{Decl, Identifier, ProcedureAnnotations, Type} import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { + lazy val KwLemmas = "lemmas" + lazy val KwLemma = "lemma" lazy val KwProof = "proof" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" @@ -61,7 +61,7 @@ class UMCParser extends l.UclidParser { lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint, KwInj, KwIndUb) + KwDisjoint, KwInj, KwIndUb, KwLemmas, KwLemma) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -169,15 +169,59 @@ class UMCParser extends l.UclidParser { case e => AssertStmt(e) } } + + lazy val LemmaDecl : PackratParser[l.ProcedureDecl] = positioned { + KwLemma ~> ProcedureAnnotationList.? ~ Id ~ IdTypeList ~ (KwReturns ~> IdTypeList) ~ + rep(ProcedureVerifExpr) ~ BlkStmt ^^ + { case annotOpt ~ id ~ args ~ outs ~ verifExprs ~ body => + val annotations = annotOpt match { + case Some(ids) => ProcedureAnnotations(ids.toSet) + case None => ProcedureAnnotations(Set.empty) + } + val verifExprList = verifExprs.flatMap(v => v) + val requiresList = collectRequires(verifExprList) + val ensuresList = collectEnsures(verifExprList) + val modifiesList = collectModifies(verifExprList) + l.ProcedureDecl(id, l.ProcedureSig(args,outs), + body, requiresList, ensuresList, modifiesList.toSet, annotations) } | + // procedure with no return value + KwLemma ~> ProcedureAnnotationList.? ~ Id ~ IdTypeList ~ rep(ProcedureVerifExpr) ~ BlkStmt ^^ + { case annotOpt ~ id ~ args ~ verifExprs ~ body => + val annotations = annotOpt match { + case Some(ids) => ProcedureAnnotations(ids.toSet) + case None => ProcedureAnnotations(Set.empty) + } + val verifExprList = verifExprs.flatMap(v => v) + val requiresList = collectRequires(verifExprList) + val ensuresList = collectEnsures(verifExprList) + val modifiesList = collectModifies(verifExprList) + l.ProcedureDecl(id, l.ProcedureSig(args, List.empty), + body, requiresList, ensuresList, modifiesList.toSet, annotations) } + } + + lazy val LemmaDeclarations: PackratParser[l.ProcedureDecl] = + positioned ( LemmaDecl ); + + lazy val LemmaBlock: PackratParser[List[Decl]] = { + KwLemmas ~ "{" ~> rep(LemmaDeclarations) <~ "}" + } + lazy val ProofStmt: PackratParser[Statement] = positioned ( Stmt ); + lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } + lazy val CntProof: PackratParser[CountingProof] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - CountingProof(id, decls, proof) + CountingProof(id, decls, List(), proof) + } + } | + KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ LemmaBlock ~ (ProofScript <~ "}") ^^ { + case id ~ decls ~ lemmas ~ proof => { + CountingProof(id, decls, lemmas, proof) } } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 97e703dd3..560a9624f 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -415,8 +415,9 @@ class UMCRewriter(cntProof : CountingProof) { l.BlockStmt(List.empty, newProofStmts), List.empty, List.empty, Set.empty, l.ProcedureAnnotations(Set.empty)) val prevDecls = cntProof.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val prevProcDecls = cntProof.lemmas val moduleP = l.Module(cntProof.id, - prevDecls ++ ufDecls ++ List(newProofProc), + prevDecls ++ prevProcDecls ++ ufDecls ++ List(newProofProc), controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl new file mode 100644 index 000000000..1706d62a0 --- /dev/null +++ b/test/modelcounter/test-lemma.ucl @@ -0,0 +1,20 @@ +module main { + define f (n: integer): boolean = 1 <= n < 10; + + lemmas{ + lemma lemma1() + returns (b: boolean) + { + b= true; + } + lemma lemma2() + returns (i: integer) + { + i= 42; + } + } + + proof { + assert range: #[(n: integer)] :: (f(n)) == 9; + } +} \ No newline at end of file From 356f3035551d9ef741177de4001365aec8af5dcb Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 20:24:14 +0530 Subject: [PATCH 034/119] verifying the lemmas in the control block --- .../scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- .../scala/uclid/extensions/modelcounts/UMCParser.scala | 2 +- .../scala/uclid/extensions/modelcounts/UMCRewriter.scala | 7 ++++++- test/modelcounter/test-lemma.ucl | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index f0403fc4c..820000d0d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -368,7 +368,7 @@ case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } -case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.Decl], +case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.ProcedureDecl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 630661146..8c0a22c4f 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -202,7 +202,7 @@ class UMCParser extends l.UclidParser { lazy val LemmaDeclarations: PackratParser[l.ProcedureDecl] = positioned ( LemmaDecl ); - lazy val LemmaBlock: PackratParser[List[Decl]] = { + lazy val LemmaBlock: PackratParser[List[l.ProcedureDecl]] = { KwLemmas ~ "{" ~> rep(LemmaDeclarations) <~ "}" } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 560a9624f..95e9a7706 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -403,6 +403,11 @@ class UMCRewriter(cntProof : CountingProof) { None, None), ) + lazy val verifyLemmas : List[l.GenericProofCommand] = + cntProof.lemmas.map(lem => l.GenericProofCommand( + l.Identifier("verify"), + List.empty, List((lem.id, lem.id.toString+ " in Lemma block.")), + None, None)) def process() : l.Module = { val countingOps = identifyCountOps(cntProof.stmts) @@ -418,7 +423,7 @@ class UMCRewriter(cntProof : CountingProof) { val prevProcDecls = cntProof.lemmas val moduleP = l.Module(cntProof.id, prevDecls ++ prevProcDecls ++ ufDecls ++ List(newProofProc), - controlBlock, List.empty) + verifyLemmas ++ controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl index 1706d62a0..8dbfe32b7 100644 --- a/test/modelcounter/test-lemma.ucl +++ b/test/modelcounter/test-lemma.ucl @@ -9,6 +9,7 @@ module main { } lemma lemma2() returns (i: integer) + ensures i > 10; { i= 42; } From 89a6fffba5af12d75e91c2150ba5472b84a3e729 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 20:51:06 +0530 Subject: [PATCH 035/119] indLB for zk-hat working now --- test/modelcounter/counting-zkhat.ucl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl index 526aba2da..8019cea75 100644 --- a/test/modelcounter/counting-zkhat.ucl +++ b/test/modelcounter/counting-zkhat.ucl @@ -12,7 +12,7 @@ module main { define V1(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (forall (i : integer) :: !Y[i]); - define W(i : boolean) : boolean = 0 <= i < 2; + define W(i : integer) : boolean = 0 <= i < 2; proof { // Proof rules 2 - 7 from paper. @@ -32,12 +32,10 @@ module main { // TODO // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - // TODO : Not Working yet - - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) >= - #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R-1)) * - #[(i: integer)] :: (W(i)) - skolems(Y[(R+1) -> i]); + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (R: integer)] :: (W(i)) + skolems(Y[(R) -> (i == 1)]); // 7. (Range) #i. W(i) == 2 assert range: #[(i: integer)] :: (W(i)) == 2; From 03b5ec178bc8d90b217d579108e5817dd3dba0df Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 16 May 2020 23:30:14 +0530 Subject: [PATCH 036/119] minor edits while playing around with examples. --- test/modelcounter/counting-zkhat.ucl | 12 ++--- test/modelcounter/debug2.ucl | 65 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 test/modelcounter/debug2.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl index 8019cea75..2d14c0701 100644 --- a/test/modelcounter/counting-zkhat.ucl +++ b/test/modelcounter/counting-zkhat.ucl @@ -4,10 +4,10 @@ module main { define V(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) - && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); define Vf(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); define V1(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (forall (i : integer) :: !Y[i]); @@ -26,19 +26,19 @@ module main { assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; // 4. (ConstEq) #Y. Vf(Y, 1) = 2 - assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) - // TODO + // TODO // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * #[(i: integer) for (R: integer)] :: (W(i)) skolems(Y[(R) -> (i == 1)]); // 7. (Range) #i. W(i) == 2 - assert range: #[(i: integer)] :: (W(i)) == 2; + assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; } } diff --git a/test/modelcounter/debug2.ucl b/test/modelcounter/debug2.ucl new file mode 100644 index 000000000..7650c33ad --- /dev/null +++ b/test/modelcounter/debug2.ucl @@ -0,0 +1,65 @@ + module main { + define V (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))); + define Vf (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))); + define V1 (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: !((Y)[i]))); + define W (i: integer): boolean = ((0 <= i) && (i < 2)); + function count_1(R: integer): integer; // line 0 + axiom assump_1 : (forall (R : integer) :: (count_1(R) >= 0)); // + function count_2(R: integer): integer; // line 0 + axiom assump_2 : (forall (R : integer) :: (count_2(R) >= 0)); // + function count_3(R: integer): integer; // line 0 + axiom assump_3 : (forall (R : integer) :: (count_3(R) >= 0)); // + function count_4(R: integer): integer; // line 0 + axiom assump_4 : (forall (R : integer) :: (count_4(R) >= 0)); // + function count_5(R: integer): integer; // line 0 + axiom assump_5 : (forall (R : integer) :: (count_5(R) >= 0)); // + procedure countingProof() returns () + { + assert ((forall (Y : [integer]boolean, R : integer) :: (((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) <==> (((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) || ((R >= 0) && (forall (i : integer) :: !((Y)[i])))))) && (forall (Y : [integer]boolean, R : integer) :: !((((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) && ((R >= 0) && (forall (i : integer) :: !((Y)[i]))))))); // line 0 + assume (forall (R : integer) :: (count_4(R) == (count_1(R) + count_5(R)))); // line 0 + { + var Y_1 : [integer]boolean; // line 26 + var R : integer; // line 26 + assert [cover, SATOnly]: (forall (R : integer) :: (R >= 0 ==> (R >= 0 && (forall (i : integer) :: !Y_1[i])))); // line 0 + } + assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) >= 1))); // line 0 + { + var Y_2 : [integer]boolean; // line 26 + var Y_3 : [integer]boolean; // line 26 + var R : integer; // line 26 + assert [cover]: (forall (R : integer) :: ((R >= 0) ==> (((true && ((R >= 0) && (forall (i : integer) :: !((Y_2)[i])))) && ((R >= 0) && (forall (i : integer) :: !((Y_3)[i])))) && distinct({Y_2}, {Y_3})))); // line 0 + } + assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) < 2))); // line 0 + { + var Y_4 : [integer]boolean; // line 29 + var Y_5 : [integer]boolean; // line 29 + var R : integer; // line 29 + assert [cover, SATOnly]: (forall (R : integer) :: ((R == 1) ==> (((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_4)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_5)[i]))))) && distinct({Y_4}, {Y_5})))); // line 0 + } + assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) >= 2))); // line 0 + { + var Y_6 : [integer]boolean; // line 29 + var Y_7 : [integer]boolean; // line 29 + var Y_8 : [integer]boolean; // line 29 + var R : integer; // line 29 + assert [cover]: (forall (R : integer) :: ((R == 1) ==> ((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_6)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_7)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_8)[i]))))) && distinct({Y_6}, {Y_7}, {Y_8})))); // line 0 + } + assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) < 3))); // line 0 + assert (forall (Y : [integer]boolean, i : integer, R : integer) :: ((((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) && ((0 <= i) && (i < 2))) ==> (((R + 1) >= 0) && (forall (i : integer) :: (((i < 0) || (i >= (R + 1))) ==> !(((Y)[R -> (i == 1)])[i])))))); // line 0 + assert (forall (Y_9 : [integer]boolean, Y_10 : [integer]boolean, i_1 : integer, i_2 : integer, R : integer) :: ((((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_9)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_10)[i]))))) && ((0 <= i_1) && (i_1 < 2))) && ((0 <= i_2) && (i_2 < 2))) && ((false || distinct(Y_9, Y_10)) || (false || distinct(i_1, i_2)))) ==> (false || distinct((Y_9)[R -> (i_1 == 1)], (Y_10)[R -> (i_2 == 1)])))); // line 0 + assume (forall (R : integer) :: (count_4((R + 1)) >= (count_4(R) * count_3(R)))); // line 0 + assume (forall (R : integer) :: (count_4((R + 1)) == count_2(R))); // line 0 + assume (forall (R : integer) :: (count_3(R) == (if(0 < 2) then 2 else 0))); // line 0 + assert (forall (R : integer) :: (count_3(R) == 2)); // line 0 + + assert (forall (R : integer) :: count_4(R+1) >= count_4(R) * 2); + assert (count_4(1) >= 2); + assert (forall (R : integer) :: (R >= 0) ==> (count_1(R) == count_4(R) - 1)); + } + control { + v = verify(countingProof /* countingProof*/); // line 0 + check; // line 0 + print_results; // line 0 + } + } + From 59743f71d0109612d1e3dee31c9e33709c09d279 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 09:19:31 +0530 Subject: [PATCH 037/119] updated vim syntax file --- vim/uclid.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vim/uclid.vim b/vim/uclid.vim index 5c2977dae..d9827be78 100644 --- a/vim/uclid.vim +++ b/vim/uclid.vim @@ -21,6 +21,7 @@ syn keyword ucl4Decl module init next control function procedure retu syn keyword ucl4Cmd unroll lazysc check print_module print_cex print_results k_induction_base k_induction_step induction clear_context synthesize_invariant set_solver_option " user labels syn keyword ucl4Constant false true +syn keyword ucl4Count proof range constUB constLB constEq UB andUB disjoint or injectivity indLB indUB skolems lemmas lemma syn match ucl4Identifier display "[A-Za-z_][A-Za-z0-9_]*" @@ -63,5 +64,6 @@ hi def link ucl4Cmd Define hi def link ucl4Operator Special hi def link ucl4Delimiter Normal hi def link ucl4Number Number +hi def link ucl4Count Keyword let b:current_syntax = "ucl" From 9ec6f6a9141ba1425d19ba7083441e36092b8f05 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 09:22:13 +0530 Subject: [PATCH 038/119] minor edit to syntax file --- vim/uclid.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/uclid.vim b/vim/uclid.vim index d9827be78..c16efd22b 100644 --- a/vim/uclid.vim +++ b/vim/uclid.vim @@ -42,7 +42,7 @@ syn match ucl4Number "\<\d\+bv\d\+\>" syn match ucl4Number "\<0[xX][0-9A-F]\+bv\d\+\>" syn match ucl4Number "\<0[bB][01]\+bv\d\+\>" syn match ucl4Delimiter "\[\|\]\|(\|)" -syn match ucl4Operator "=\|==\|+\|-\|*\|&&\|||\|^\|!\|==>\|<==>\|<\|<=\|>\|>=" +syn match ucl4Operator "=\|==\|+\|-\|*\|&&\|||\|^\|!\|==>\|<==>\|<\|<=\|>\|>=\|#" syn match ucl4UComparator "<_u\|<=_u\|>_u\|>=_u" syn region ucl4MultilineComment start="/\*" end="\*/" From 2b2c951033cb700033bf6300f3a0c78877c685c9 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 10:56:18 +0530 Subject: [PATCH 039/119] added assumptions to range statement to cater symbolic bounds --- .../uclid/extensions/modelcounts/UMCLanguage.scala | 10 +++++----- .../scala/uclid/extensions/modelcounts/UMCParser.scala | 7 ++++++- .../uclid/extensions/modelcounts/UMCRewriter.scala | 2 +- test/modelcounter/test-range-symb.ucl | 8 ++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 test/modelcounter/test-range-symb.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 820000d0d..ea75bddf7 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -114,7 +114,7 @@ case class OrStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Sta } } -case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { +case class RangeStmt(op : CountingOp, cnt : l.Expr, assump: l.Expr) extends Statement { lazy val lb : l.Expr = { op.e match { case l.OperatorApplication(l.ConjunctionOp(), @@ -146,14 +146,14 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { } override val hashId = 130002 override val md5hashCode = computeMD5Hash(op, cnt) - override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) + override def toLines = List("assert range: " + assump.toString + " ==> " + op.toString() + " == " + cnt.toString()) override val countingOps = Seq(op) override val expressions = Seq(op, cnt) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(op.e), rewriter(cnt)) match { - case (Some(ep), Some(cntp)) => + (rewriter(op.e), rewriter(cnt), rewriter(assump)) match { + case (Some(ep), Some(cntp), Some(aP)) => val op1 = CountingOp(op.xs, op.ys, ep) - Some(RangeStmt(op1, cntp)) + Some(RangeStmt(op1, cntp, aP)) case _ => None } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 8c0a22c4f..1f80cd9e8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -104,7 +104,12 @@ class UMCParser extends l.UclidParser { } | KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { case e1 ~ e2 => { - RangeStmt(e1, e2) + RangeStmt(e1, e2, l.BoolLit(true)) + } + } | + KwAssert ~ KwRange ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { + case assump ~ e1 ~ e2 => { + RangeStmt(e1, e2, assump) } } | KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 95e9a7706..2cadcaef0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -154,7 +154,7 @@ class UMCRewriter(cntProof : CountingProof) { val ufn = _apply(ufMap(st.op)) val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) - val assertExpr = E.forall(st.op.ys, E.eq(ufn, st.cnt)) + val assertExpr = E.forall(st.op.ys, E.implies(st.assump, E.eq(ufn, st.cnt))) val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } diff --git a/test/modelcounter/test-range-symb.ucl b/test/modelcounter/test-range-symb.ucl new file mode 100644 index 000000000..a5a27398b --- /dev/null +++ b/test/modelcounter/test-range-symb.ucl @@ -0,0 +1,8 @@ +module main { + + define f(i : integer, j: integer, R: integer) : boolean = j <= i < R; + + proof { + assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (f(i, j, R)) == (R - j); + } +} From 595cf61e613c598e3224170f828a08d9180031bd Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 12:54:23 +0530 Subject: [PATCH 040/119] attempting Array shuffle example - constEQ going UNDEF --- .../extensions/modelcounts/UMCLanguage.scala | 4 +- .../extensions/modelcounts/UMCRewriter.scala | 6 +-- test/modelcounter/counting-array-shuffle.ucl | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/modelcounter/counting-array-shuffle.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index ea75bddf7..72f8fad55 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -225,9 +225,9 @@ case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends St } case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { - assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size >= 1) assert (fp.ys(0)._2.isInt) - assert (fp.ys == f.ys && f.ys == g.ys) + assert (fp.ys == f.ys) val n = fp.ys(0)._1 assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2cadcaef0..5cf2eabfe 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -308,7 +308,7 @@ class UMCRewriter(cntProof : CountingProof) { (indlb.n.asInstanceOf[l.Expr] -> nplus1) val conseq = new ExprRewriter(skSubs).rewrite(f_xn) val impl = E.implies(ante, conseq) - val qVars = f.xs ++ g.xs ++ f.ys + val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys val qOp = E.forall(qVars, impl) val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) @@ -339,7 +339,7 @@ class UMCRewriter(cntProof : CountingProof) { val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ - (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys + (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) // Finally, we have to produce the assumption. @@ -347,7 +347,7 @@ class UMCRewriter(cntProof : CountingProof) { val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) - val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl new file mode 100644 index 000000000..4c7d8e042 --- /dev/null +++ b/test/modelcounter/counting-array-shuffle.ucl @@ -0,0 +1,38 @@ +// Model counting in Array Shuffle + +module main { + + define V(Y : [integer]integer, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: 0 <= i < R ==> i <= Y[i] <= R) + && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); + + define Vf(Y : [integer]integer, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> (Y[i] == 0)); + + define W(i : integer, j: integer, R: integer) : boolean = j <= i < R; + + + proof { + + // (ConstEq) #Y. V(Y, 1) = 1 + // assert constEq: R == 1 ==> #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) == 1; + + // Proving for Vf (NOT REQUIRED) + // (IndLB) #Y. Vf(Y, R) >= #Y. Vf(Y, R-1) * #i. W(i, j, R) + assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) + skolems(Y[(R) -> i]); + + // Proving for V + // (IndLB) #Y. V(Y, R) >= #Y. V(Y, R-1) * #i. W(i, j, R) + assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R + 1)) >= + #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) * + #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) + skolems(Y[(R) -> i]); + + // (Range) #i. W(i, j, R) == R - j + assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) == (R - j); + } +} + From 4fc0a232e357bae41888a996ed451bff2a50f5fb Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 15:56:40 +0530 Subject: [PATCH 041/119] fixed ite printing --- src/main/scala/uclid/lang/UclidLanguage.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 08ea601a9..413c3d5fe 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -746,6 +746,8 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString + case ITEOp() => + "(if (" + operands(0).toString + ") then (" + operands(1).toString + ") else (" + operands(2).toString + "))" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => From 6ffabb8f6be3061f8962874e44fcd5c36b16176f Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 16:49:23 +0530 Subject: [PATCH 042/119] fixed satonly decorator and assert printing --- src/main/scala/uclid/lang/UclidLanguage.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 413c3d5fe..44aa3c7d3 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -849,7 +849,7 @@ case object CoverDecorator extends ExprDecorator { override val md5hashCode = computeMD5Hash } case object SATOnlyDecorator extends ExprDecorator { - override def toString = "satonly" + override def toString = "SATOnly" override val hashId = 2606 override val md5hashCode = computeMD5Hash } @@ -1160,7 +1160,7 @@ case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDec override def toLines = { val name = "assert" val decoratorStr = if (decorators.size > 0) { - " [" + Utils.join(decorators.map(_.toString()), ", ") + "] " + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] :" } else { "" } val prefix = id match { case Some(n) => name + " " + n.toString() + decoratorStr + ": " From 598a14d1c561a9c16a41095baacfb079a1afbaea Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 20:54:13 +0530 Subject: [PATCH 043/119] UFs can take more than one arguements now in IndLB --- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 5cf2eabfe..03097a0bb 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -345,14 +345,12 @@ class UMCRewriter(cntProof : CountingProof) { // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) - val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1) ++ ufMap(f).sig.args.drop(1).map(_._1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) - val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } From 8e1a6cb671d1bf5453c3b3b2f08fe883b1132ad5 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 20:56:20 +0530 Subject: [PATCH 044/119] indLB can take more than one ys values --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 72f8fad55..07d54895a 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -225,7 +225,7 @@ case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends St } case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { - assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size >= 1) + assert (fp.ys.size >= 1 && f.ys.size >= 1 && g.ys.size >= 1) assert (fp.ys(0)._2.isInt) assert (fp.ys == f.ys) val n = fp.ys(0)._1 From cb4aa7cb29cb381894875a1d7db7151b830120c7 Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 11:51:34 +0530 Subject: [PATCH 045/119] asserts can be named --- src/main/scala/uclid/lang/UclidParser.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index ca1f33574..808e836c0 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -464,6 +464,13 @@ class UclidParser extends UclidTokenParsers with PackratParsers { case ids ~ e => AssertStmt(e, None, ids.map(ExprDecorator.parse(_))) } | + KwAssert ~> (Id <~ ":") ~ Expr <~ ";" ^^ { + case id ~ e => AssertStmt(e, Some(id), List.empty) + } | + KwAssert ~> (Id) ~ ("[" ~> IdList <~ "]" ~ ":") ~ Expr <~ ";" ^^ { + case id ~ ids ~ e => + AssertStmt(e, Some(id), ids.map(ExprDecorator.parse(_))) + } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ From a333c364ba314bd3e31f8999d2402fbeb5e8ef2a Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 12:16:48 +0530 Subject: [PATCH 046/119] fixing printing of 2 : in case asserts have names --- src/main/scala/uclid/lang/UclidLanguage.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 44aa3c7d3..8647b871b 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -1160,11 +1160,17 @@ case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDec override def toLines = { val name = "assert" val decoratorStr = if (decorators.size > 0) { - " [" + Utils.join(decorators.map(_.toString()), ", ") + "] :" - } else { "" } + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] : " + } else { " " } val prefix = id match { - case Some(n) => name + " " + n.toString() + decoratorStr + ": " - case None => name + " " + decoratorStr + case Some(n) => + if (decorators.nonEmpty) { + name + " " + n.toString() + decoratorStr + } + else { + name + " " + n.toString() + " : " + } + case None => name + decoratorStr } List(prefix + e.toString() + "; // " + position.toString) } From 60f345e472a2e0b9d62f5c23b27b01e258472d03 Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 12:27:22 +0530 Subject: [PATCH 047/119] added descriptive names to rewritten asserts --- .../extensions/modelcounts/UMCRewriter.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 03097a0bb..3d025192c 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -141,7 +141,7 @@ class UMCRewriter(cntProof : CountingProof) { val f3 = o3.e val assertExpr = E.and(E.forall(args, E.iff(f1, E.or(f2, f3))), E.forall(args, E.not(E.and(f2, f3)))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Or")), List.empty) val ufn1 = _apply(ufMap(o1)) val ufn2 = _apply(ufMap(o2)) val ufn3 = _apply(ufMap(o3)) @@ -155,7 +155,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) val assertExpr = E.forall(st.op.ys, E.implies(st.assump, E.eq(ufn, st.cnt))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Range")), List.empty) List(assumeStmt, assertStmt) } @@ -176,7 +176,7 @@ class UMCRewriter(cntProof : CountingProof) { val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) val query = E.forall(qVars, E.implies(ante, E.and(conjunction, distincts))) - val assertStmt = l.AssertStmt(query, None, decorators) + val assertStmt = l.AssertStmt(query, Some(l.Identifier("ConstantBound")), decorators) BlockStmt(blkDecls, List(assertStmt)) } def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { @@ -202,7 +202,7 @@ class UMCRewriter(cntProof : CountingProof) { val f = s1.e val g = s2.e val assertExpr = E.forall(args, E.implies(f, g)) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("UB")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, ufn2)) @@ -221,7 +221,7 @@ class UMCRewriter(cntProof : CountingProof) { val f2 = s2.e val f3 = s3.e val assertExpr = E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("AndUB")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val ufn3 = _apply(ufMap(s3)) @@ -241,7 +241,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl = E.implies(f_x, conseq) val qVars = f.xs ++ f.ys val qOp = E.forall(qVars, impl) - val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("Injectivity_SkolemApplication")), List.empty) // Next we want to show injectivity of the skolem: // f(x1) && f(x2) && (x1 != x2) ==> skolem(x1) != skolem(x2) @@ -260,7 +260,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl2 = E.implies(ante2, skdiff) val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("Injectivity_SkolemInjectivity")), List.empty) // Now the assumption. val ufn = _apply(ufMap(f)) @@ -287,7 +287,7 @@ class UMCRewriter(cntProof : CountingProof) { val f2 = s2.e val f3 = s3.e val assertExpr = E.and(E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))), intersection) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Disjoint")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val ufn3 = _apply(ufMap(s3)) @@ -310,7 +310,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl = E.implies(ante, conseq) val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys val qOp = E.forall(qVars, impl) - val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndLB_SkolemApplication")), List.empty) // Now we want to show injectivity of the skolem: // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) @@ -340,7 +340,7 @@ class UMCRewriter(cntProof : CountingProof) { (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) From c6116481fc8658f5525d418d840923e7f585cbca Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 21:36:10 +0530 Subject: [PATCH 048/119] fixed repetition of quantified variables in indLB --- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 3d025192c..1b2b235c0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -309,7 +309,7 @@ class UMCRewriter(cntProof : CountingProof) { val conseq = new ExprRewriter(skSubs).rewrite(f_xn) val impl = E.implies(ante, conseq) val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys - val qOp = E.forall(qVars, impl) + val qOp = E.forall(qVars.distinct, impl) val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndLB_SkolemApplication")), List.empty) // Now we want to show injectivity of the skolem: @@ -340,7 +340,7 @@ class UMCRewriter(cntProof : CountingProof) { (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars.distinct, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) From 18db170f982662812f9dd2866b5448f4431015e2 Mon Sep 17 00:00:00 2001 From: ssahai Date: Tue, 19 May 2020 16:24:51 +0530 Subject: [PATCH 049/119] induction working for array shuffle --- test/modelcounter/counting-array-shuffle.ucl | 39 +++++++------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl index 4c7d8e042..049fb38a8 100644 --- a/test/modelcounter/counting-array-shuffle.ucl +++ b/test/modelcounter/counting-array-shuffle.ucl @@ -2,37 +2,26 @@ module main { - define V(Y : [integer]integer, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: 0 <= i < R ==> i <= Y[i] <= R) - && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); - - define Vf(Y : [integer]integer, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> (Y[i] == 0)); - - define W(i : integer, j: integer, R: integer) : boolean = j <= i < R; + define V(Y : [integer]integer, R : integer, N: integer) : boolean = + (0 <= R < N) && (forall (i : integer) :: 0 <= i < R ==> i < Y[i] <= N) + && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); + define W(j : integer, R: integer, N: integer) : boolean = R+1 <= j < N; proof { - // (ConstEq) #Y. V(Y, 1) = 1 - // assert constEq: R == 1 ==> #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) == 1; - - // Proving for Vf (NOT REQUIRED) - // (IndLB) #Y. Vf(Y, R) >= #Y. Vf(Y, R-1) * #i. W(i, j, R) - assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R + 1)) >= - #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R)) * - #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) - skolems(Y[(R) -> i]); + // (ConstEq) #Y. V(Y, 1, N) = N + // assert constEq: (R == 1 && N > R) ==> #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) == N; // Proving for V - // (IndLB) #Y. V(Y, R) >= #Y. V(Y, R-1) * #i. W(i, j, R) - assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R + 1)) >= - #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) * - #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) - skolems(Y[(R) -> i]); - - // (Range) #i. W(i, j, R) == R - j - assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) == (R - j); + // (IndLB) #Y. V(Y, R+1, N) >= #Y. V(Y, R, N) * #i. W(i, R, N) + assert indLB: #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R + 1, N)) >= + #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) * + #[(j: integer) for (R: integer, N: integer)] :: (W(j, R, N)) + skolems(Y[(R) -> j]); + + // (Range) #i. W(i, R, N) == N - (R + 1) + assert range: (0 < R < N) ==> #[(i: integer) for (R: integer, N: integer)] :: (W(i, R, N)) == (N - (R + 1)); } } From ebce0388290220fe124980319fd8ccee18d9723f Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Wed, 23 Sep 2020 17:44:32 +0530 Subject: [PATCH 050/119] added test for indLB. Working fine now --- test/modelcounter/test-indlb.ucl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/modelcounter/test-indlb.ucl diff --git a/test/modelcounter/test-indlb.ucl b/test/modelcounter/test-indlb.ucl new file mode 100644 index 000000000..8dcd9cf67 --- /dev/null +++ b/test/modelcounter/test-indlb.ucl @@ -0,0 +1,11 @@ +module main { + define X(j: integer) : boolean = 0 <= j < 2; + define U(arr: [integer]integer, n: integer): boolean = + (n >= 0) && (forall (i: integer) :: (i < 0 || i >= n) ==> arr[i] == 0) && (forall (i: integer) :: (i >= 0 || i < n) ==> (0 <= arr[i] < 2)); + proof { + assert indLB: #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n+1)) >= + #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n)) * + #[(j : integer) for (n : integer)] :: (X(j)) + skolems (arr[n -> j]); + } +} \ No newline at end of file From 719e4a3c3ef2b3b67db2788d547a8f9be71af2be Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Thu, 24 Sep 2020 18:24:20 +0530 Subject: [PATCH 051/119] added indUB --- .../extensions/modelcounts/UMCRewriter.scala | 64 +++++++++++++++++++ test/modelcounter/test-indub.ucl | 11 ++++ 2 files changed, 75 insertions(+) create mode 100644 test/modelcounter/test-indub.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 1b2b235c0..805e7ceb6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -354,6 +354,68 @@ class UMCRewriter(cntProof : CountingProof) { List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } + def rewriteIndUb(ufMap : UFMap, indub : IndUbStmt) : List[l.Statement] = { + // First we want f(x, n) ==> f(skolem_x(x, n), n - 1) && g(skolem_y(x, n), n - 1) + val fp = indub.fp + val f = indub.f + val g = indub.g + val f_xn = f.e + val g_yn = g.e + val ante = fp.e + val skSubs = (f.xs.map(_._1.asInstanceOf[l.Expr]) zip indub.skolems.lift(0)).toMap ++ + (g.xs.map(_._1.asInstanceOf[l.Expr]) zip indub.skolems.lift(1)).toMap + + val conseq_f_subs = new ExprRewriter(skSubs).rewrite(f_xn) + val conseq_g_subs = new ExprRewriter(skSubs).rewrite(g_yn) + + // add base case of induction to consequent. i.e. n+1 == 0 + val nplus1 = E.plus(indub.n, l.IntLit(1)) + val conseq_base = E.eq(nplus1,l.IntLit(0)) + + val conseq = E.or( E.and(conseq_f_subs, conseq_g_subs), conseq_base) + val impl = E.implies(ante, conseq) + val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys + val qOp = E.forall(qVars.distinct, impl) + val lowerAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndUB_SkolemApplication")), List.empty) + + // Now we want to show injectivity of the skolem: + // f(x1, n+1) && f(x2, n+1) && (x1 != x2) + // ==> skolem_x(x1, n) != skolem_x(x2,n) || skolem_y(x1,n) != skolem_y(x2, n) + val x1s = fp.xs.map(p => generateId(p._1.toString())) + val x2s = fp.xs.map(p => generateId(p._1.toString())) + + val rwx1 = new ExprRewriter((fp.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((fp.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + + val f_x1n = rwx1.rewrite(fp.e) + val f_x2n = rwx2.rewrite(fp.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + + val ante2 = E.andL(List(f_x1n, f_x2n, xdiff)) + + val sk_x1 = indub.skolems.map(sk => rwx1.rewrite(sk)) + val sk_x2 = indub.skolems.map(sk => rwx2.rewrite(sk)) + + val skdiff = E.orL((sk_x1 zip sk_x2).map(p => E.distinct(p._1, p._2))) + + val impl2 = E.implies(ante2, skdiff) + + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars.distinct, impl2), Some(l.Identifier("IndUB_SkolemInjectivity")), List.empty) + + // Finally, we have to produce the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1) ++ ufMap(f).sig.args.drop(1).map(_._1)) + val geqExpr = E.le(ufnplus1, E.mul(ufn, ugn)) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) + val ufpn = _apply(ufMap(indub.fp)) + val eqExpr = E.eq(ufnplus1, ufpn) + val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) + List(lowerAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { val newStmts : List[l.Statement] = st match { @@ -380,6 +442,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteDisjoint(ufmap, disj) case inj : InjectivityStmt => rewriteInjectivity(ufmap, inj) + case indUb : IndUbStmt => + rewriteIndUb(ufmap, indUb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-indub.ucl b/test/modelcounter/test-indub.ucl new file mode 100644 index 000000000..1f3418c45 --- /dev/null +++ b/test/modelcounter/test-indub.ucl @@ -0,0 +1,11 @@ +module main { + define X(j: integer) : boolean = 0 <= j < 2; + define U(arr: [integer]integer, n: integer): boolean = + (n >= 0) && (forall (i: integer) :: (i < 0 || i >= n) ==> arr[i] == 0) && (forall (i: integer) :: (i >= 0 || i < n) ==> (0 <= arr[i] < 2)); + proof { + assert indUB: #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n+1)) <= + #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n)) * + #[(j : integer) for (n : integer)] :: (X(j)) + skolems (arr[n -> 0], arr[n]); + } +} \ No newline at end of file From bf0440866c3a1c9bac4264ec66b100f9e75ce43e Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Fri, 25 Sep 2020 11:21:51 +0530 Subject: [PATCH 052/119] updated/added tests for all the proof rules --- test/modelcounter/counting-array-shuffle.ucl | 27 -------- test/modelcounter/counting-zkhat.ucl | 44 ------------- test/modelcounter/counting_2n.ucl | 36 ---------- test/modelcounter/debug2.ucl | 65 ------------------- test/modelcounter/test-constLB.ucl | 9 +++ test/modelcounter/test-constUB.ucl | 6 ++ .../{test-indlb.ucl => test-indLB.ucl} | 0 .../{test-indub.ucl => test-indUB.ucl} | 2 +- test/modelcounter/test-lemma.ucl | 21 ------ test/modelcounter/{hello.ucl => test-or.ucl} | 5 -- test/modelcounter/test-range.ucl | 8 +++ 11 files changed, 24 insertions(+), 199 deletions(-) delete mode 100644 test/modelcounter/counting-array-shuffle.ucl delete mode 100644 test/modelcounter/counting-zkhat.ucl delete mode 100644 test/modelcounter/counting_2n.ucl delete mode 100644 test/modelcounter/debug2.ucl create mode 100644 test/modelcounter/test-constLB.ucl create mode 100644 test/modelcounter/test-constUB.ucl rename test/modelcounter/{test-indlb.ucl => test-indLB.ucl} (100%) rename test/modelcounter/{test-indub.ucl => test-indUB.ucl} (99%) delete mode 100644 test/modelcounter/test-lemma.ucl rename test/modelcounter/{hello.ucl => test-or.ucl} (58%) create mode 100644 test/modelcounter/test-range.ucl diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl deleted file mode 100644 index 049fb38a8..000000000 --- a/test/modelcounter/counting-array-shuffle.ucl +++ /dev/null @@ -1,27 +0,0 @@ -// Model counting in Array Shuffle - -module main { - - define V(Y : [integer]integer, R : integer, N: integer) : boolean = - (0 <= R < N) && (forall (i : integer) :: 0 <= i < R ==> i < Y[i] <= N) - && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); - - define W(j : integer, R: integer, N: integer) : boolean = R+1 <= j < N; - - proof { - - // (ConstEq) #Y. V(Y, 1, N) = N - // assert constEq: (R == 1 && N > R) ==> #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) == N; - - // Proving for V - // (IndLB) #Y. V(Y, R+1, N) >= #Y. V(Y, R, N) * #i. W(i, R, N) - assert indLB: #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R + 1, N)) >= - #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) * - #[(j: integer) for (R: integer, N: integer)] :: (W(j, R, N)) - skolems(Y[(R) -> j]); - - // (Range) #i. W(i, R, N) == N - (R + 1) - assert range: (0 < R < N) ==> #[(i: integer) for (R: integer, N: integer)] :: (W(i, R, N)) == (N - (R + 1)); - } -} - diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl deleted file mode 100644 index 2d14c0701..000000000 --- a/test/modelcounter/counting-zkhat.ucl +++ /dev/null @@ -1,44 +0,0 @@ -// Model counting in ZK Hats - Motivating example in CAV 2020 paper - -module main { - - define V(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) - && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); - - define Vf(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); - - define V1(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: !Y[i]); - - define W(i : integer) : boolean = 0 <= i < 2; - - proof { - // Proof rules 2 - 7 from paper. - - // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) - assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == - #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + - #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); - - // 3. (ConstEq) #Y. V1(Y, R) = 1 - assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; - - // 4. (ConstEq) #Y. Vf(Y, 1) = 2 - assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; - - // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) - // TODO - - // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= - #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * - #[(i: integer) for (R: integer)] :: (W(i)) - skolems(Y[(R) -> (i == 1)]); - - // 7. (Range) #i. W(i) == 2 - assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; - - } -} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl deleted file mode 100644 index d270863e8..000000000 --- a/test/modelcounter/counting_2n.ucl +++ /dev/null @@ -1,36 +0,0 @@ -module main { - - // U is the function V_f in the paper. - define U(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); - // V is the function V in the paper. - define V(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); - // W is the function V_1 in the paper. - define W(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: !arr[i]); - define X(b : boolean) : boolean = true; - - proof { - // The satisfying assignments to U are the disjoint union of the satisfying - // assignments to V and W. - assert or: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == - #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + - #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); - // prove #arr: W(arr, n) == 1 - assert constEq: (n >= 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; - // prove #arr: U(arr, 1) == 1 - assert constEq: (n == 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == 1; - // prove #i: X(b) == 2 - assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; - assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; - // prove the inductive step in the count. - // #arr. U(arr, n + 1) >= #arr. U(arr, n) * 2 - assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= - #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * - #[(b : boolean) for (n : integer)] :: (X(b)) - skolems (arr[n -> b]); - } -} - diff --git a/test/modelcounter/debug2.ucl b/test/modelcounter/debug2.ucl deleted file mode 100644 index 7650c33ad..000000000 --- a/test/modelcounter/debug2.ucl +++ /dev/null @@ -1,65 +0,0 @@ - module main { - define V (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))); - define Vf (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))); - define V1 (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: !((Y)[i]))); - define W (i: integer): boolean = ((0 <= i) && (i < 2)); - function count_1(R: integer): integer; // line 0 - axiom assump_1 : (forall (R : integer) :: (count_1(R) >= 0)); // - function count_2(R: integer): integer; // line 0 - axiom assump_2 : (forall (R : integer) :: (count_2(R) >= 0)); // - function count_3(R: integer): integer; // line 0 - axiom assump_3 : (forall (R : integer) :: (count_3(R) >= 0)); // - function count_4(R: integer): integer; // line 0 - axiom assump_4 : (forall (R : integer) :: (count_4(R) >= 0)); // - function count_5(R: integer): integer; // line 0 - axiom assump_5 : (forall (R : integer) :: (count_5(R) >= 0)); // - procedure countingProof() returns () - { - assert ((forall (Y : [integer]boolean, R : integer) :: (((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) <==> (((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) || ((R >= 0) && (forall (i : integer) :: !((Y)[i])))))) && (forall (Y : [integer]boolean, R : integer) :: !((((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) && ((R >= 0) && (forall (i : integer) :: !((Y)[i]))))))); // line 0 - assume (forall (R : integer) :: (count_4(R) == (count_1(R) + count_5(R)))); // line 0 - { - var Y_1 : [integer]boolean; // line 26 - var R : integer; // line 26 - assert [cover, SATOnly]: (forall (R : integer) :: (R >= 0 ==> (R >= 0 && (forall (i : integer) :: !Y_1[i])))); // line 0 - } - assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) >= 1))); // line 0 - { - var Y_2 : [integer]boolean; // line 26 - var Y_3 : [integer]boolean; // line 26 - var R : integer; // line 26 - assert [cover]: (forall (R : integer) :: ((R >= 0) ==> (((true && ((R >= 0) && (forall (i : integer) :: !((Y_2)[i])))) && ((R >= 0) && (forall (i : integer) :: !((Y_3)[i])))) && distinct({Y_2}, {Y_3})))); // line 0 - } - assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) < 2))); // line 0 - { - var Y_4 : [integer]boolean; // line 29 - var Y_5 : [integer]boolean; // line 29 - var R : integer; // line 29 - assert [cover, SATOnly]: (forall (R : integer) :: ((R == 1) ==> (((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_4)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_5)[i]))))) && distinct({Y_4}, {Y_5})))); // line 0 - } - assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) >= 2))); // line 0 - { - var Y_6 : [integer]boolean; // line 29 - var Y_7 : [integer]boolean; // line 29 - var Y_8 : [integer]boolean; // line 29 - var R : integer; // line 29 - assert [cover]: (forall (R : integer) :: ((R == 1) ==> ((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_6)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_7)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_8)[i]))))) && distinct({Y_6}, {Y_7}, {Y_8})))); // line 0 - } - assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) < 3))); // line 0 - assert (forall (Y : [integer]boolean, i : integer, R : integer) :: ((((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) && ((0 <= i) && (i < 2))) ==> (((R + 1) >= 0) && (forall (i : integer) :: (((i < 0) || (i >= (R + 1))) ==> !(((Y)[R -> (i == 1)])[i])))))); // line 0 - assert (forall (Y_9 : [integer]boolean, Y_10 : [integer]boolean, i_1 : integer, i_2 : integer, R : integer) :: ((((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_9)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_10)[i]))))) && ((0 <= i_1) && (i_1 < 2))) && ((0 <= i_2) && (i_2 < 2))) && ((false || distinct(Y_9, Y_10)) || (false || distinct(i_1, i_2)))) ==> (false || distinct((Y_9)[R -> (i_1 == 1)], (Y_10)[R -> (i_2 == 1)])))); // line 0 - assume (forall (R : integer) :: (count_4((R + 1)) >= (count_4(R) * count_3(R)))); // line 0 - assume (forall (R : integer) :: (count_4((R + 1)) == count_2(R))); // line 0 - assume (forall (R : integer) :: (count_3(R) == (if(0 < 2) then 2 else 0))); // line 0 - assert (forall (R : integer) :: (count_3(R) == 2)); // line 0 - - assert (forall (R : integer) :: count_4(R+1) >= count_4(R) * 2); - assert (count_4(1) >= 2); - assert (forall (R : integer) :: (R >= 0) ==> (count_1(R) == count_4(R) - 1)); - } - control { - v = verify(countingProof /* countingProof*/); // line 0 - check; // line 0 - print_results; // line 0 - } - } - diff --git a/test/modelcounter/test-constLB.ucl b/test/modelcounter/test-constLB.ucl new file mode 100644 index 000000000..95bed7400 --- /dev/null +++ b/test/modelcounter/test-constLB.ucl @@ -0,0 +1,9 @@ +module main { + + define f(n : integer) : boolean = 1 <= n < 10; + + proof { + assert constLB: #[(n:integer)] :: (f(n)) >= 5; + assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; + } +} diff --git a/test/modelcounter/test-constUB.ucl b/test/modelcounter/test-constUB.ucl new file mode 100644 index 000000000..131ca6c88 --- /dev/null +++ b/test/modelcounter/test-constUB.ucl @@ -0,0 +1,6 @@ +module main { + + proof { + assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; + } +} diff --git a/test/modelcounter/test-indlb.ucl b/test/modelcounter/test-indLB.ucl similarity index 100% rename from test/modelcounter/test-indlb.ucl rename to test/modelcounter/test-indLB.ucl diff --git a/test/modelcounter/test-indub.ucl b/test/modelcounter/test-indUB.ucl similarity index 99% rename from test/modelcounter/test-indub.ucl rename to test/modelcounter/test-indUB.ucl index 1f3418c45..0d0e4eba8 100644 --- a/test/modelcounter/test-indub.ucl +++ b/test/modelcounter/test-indUB.ucl @@ -8,4 +8,4 @@ module main { #[(j : integer) for (n : integer)] :: (X(j)) skolems (arr[n -> 0], arr[n]); } -} \ No newline at end of file +} diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl deleted file mode 100644 index 8dbfe32b7..000000000 --- a/test/modelcounter/test-lemma.ucl +++ /dev/null @@ -1,21 +0,0 @@ -module main { - define f (n: integer): boolean = 1 <= n < 10; - - lemmas{ - lemma lemma1() - returns (b: boolean) - { - b= true; - } - lemma lemma2() - returns (i: integer) - ensures i > 10; - { - i= 42; - } - } - - proof { - assert range: #[(n: integer)] :: (f(n)) == 9; - } -} \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/test-or.ucl similarity index 58% rename from test/modelcounter/hello.ucl rename to test/modelcounter/test-or.ucl index 95c10e6b9..e2fc2b748 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/test-or.ucl @@ -5,12 +5,7 @@ module main { define h(n : integer) : boolean = 5 <= n < 11; proof { - assert range: #[(n:integer)] :: (f(n)) == 16; - assert constLB: #[(n:integer)] :: (g(n)) >= 5; - assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; - assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; assert or: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } - diff --git a/test/modelcounter/test-range.ucl b/test/modelcounter/test-range.ucl new file mode 100644 index 000000000..4a25a259a --- /dev/null +++ b/test/modelcounter/test-range.ucl @@ -0,0 +1,8 @@ +module main { + + define f(n : integer) : boolean = 1 <= n < 21; + + proof { + assert range: #[(n:integer)] :: (f(n)) == 20; + } +} From 29e99db6c759da24fde6ffbc47a6fe69d7dda873 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Fri, 25 Sep 2020 11:30:00 +0530 Subject: [PATCH 053/119] minor edit to fix warning while building the universal package --- src/main/scala/uclid/smt/SMTLIB2Interface.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/smt/SMTLIB2Interface.scala b/src/main/scala/uclid/smt/SMTLIB2Interface.scala index 613054e88..b0670f02d 100644 --- a/src/main/scala/uclid/smt/SMTLIB2Interface.scala +++ b/src/main/scala/uclid/smt/SMTLIB2Interface.scala @@ -170,7 +170,7 @@ trait SMTLIB2Base { /** * Translates an smt operator to its string representation. * - * @opIn The smt operator to be translated. + * @param opIn The smt operator to be translated. */ def translateOp(opIn: Operator) : String = { opIn match { From 103a771614b0660d20ed633623ef08a602d3d82b Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Wed, 30 Sep 2020 15:01:54 +0530 Subject: [PATCH 054/119] update author info in modelcounting extension --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCMain.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCParser.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 07d54895a..b82f370ad 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -30,7 +30,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Main file for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 45232ec69..bf92782a8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -30,7 +30,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Main file for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1f80cd9e8..4d63b6a8b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -31,7 +31,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Parser for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 805e7ceb6..3fbc78359 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -31,7 +31,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Rewriter for the UCLID5 model counter. * From ca5f2bd8caa89f17f621e86a3cf879ad7946ac01 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Thu, 1 Oct 2020 13:08:31 +0530 Subject: [PATCH 055/119] config options works with UMC now --- src/main/scala/uclid/extensions/modelcounts/UMCMain.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index bf92782a8..133fa32e8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -44,8 +44,7 @@ import uclid.Utils object UMCMain { /** Executes regular UCLID5 on the processed module. */ - def runProcessedModel(module : l.Module) : Unit = { - val config = UclidMain.Config() + def runProcessedModel(module : l.Module, config: UclidMain.Config) : Unit = { val mainModuleName = l.Identifier("main") val modules = UclidMain.compileModules(List(module), mainModuleName, false) val mainModule = UclidMain.instantiate(config, modules, mainModuleName, true) @@ -55,7 +54,7 @@ object UMCMain { throw new Utils.ParserError("Unable to find main module", None, None) } } - + def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) @@ -63,6 +62,6 @@ object UMCMain { val moduleP = new UMCRewriter(module).process() println("\nModule after rewriting: ") println(moduleP.toString()) - runProcessedModel(moduleP) + runProcessedModel(moduleP, config) } } From 6502265e4a85688ff92d963419c1957ef3cb73b5 Mon Sep 17 00:00:00 2001 From: polgreen Date: Tue, 27 Oct 2020 11:18:52 -0700 Subject: [PATCH 056/119] Add array reads to readset Redundant assignment eliminator was removing relevant assigns because it was failing to count array select statements as reads of the index variable, if the array select happens on the LHS of an assign. Bug exposed by test/test-redundant-assign.ucl --- .../scala/uclid/lang/StatementScheduler.scala | 10 +++++- src/test/scala/VerifierSpec.scala | 3 ++ test/test-redundant-assign.ucl | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/test-redundant-assign.ucl diff --git a/src/main/scala/uclid/lang/StatementScheduler.scala b/src/main/scala/uclid/lang/StatementScheduler.scala index 324a44fb0..eea0b16ee 100644 --- a/src/main/scala/uclid/lang/StatementScheduler.scala +++ b/src/main/scala/uclid/lang/StatementScheduler.scala @@ -185,7 +185,15 @@ object StatementScheduler { case AssertStmt(e, _) => readSet(e) case AssumeStmt(e, _) => readSet(e) case HavocStmt(_) => Set.empty - case AssignStmt(_, rhss) => readSets(rhss) + case AssignStmt(lhss, rhss) => + { + val arrayIndices = + lhss flatMap { + case arrayselect : LhsArraySelect => Some(arrayselect.indices) + case _ => None + } + readSets(rhss)++readSets(arrayIndices.flatten) + } case BlockStmt(vars, stmts) => val declaredVars : Set[Identifier] = vars.flatMap(vs => vs.ids.map(v => v)).toSet readSets(stmts, context + vars) -- declaredVars diff --git a/src/test/scala/VerifierSpec.scala b/src/test/scala/VerifierSpec.scala index 69769fc23..5ed949c23 100644 --- a/src/test/scala/VerifierSpec.scala +++ b/src/test/scala/VerifierSpec.scala @@ -434,6 +434,9 @@ class ModuleVerifSpec extends AnyFlatSpec { "issue-187b.ucl" should "verify all assertions." in { VerifierSpec.expectedFails("./test/issue-187b.ucl", 0) } + "test-redundant-assign.ucl" should "verify all assertions." in { + VerifierSpec.expectedFails("./test/test-redundant-assign.ucl", 0) + } } class LTLVerifSpec extends AnyFlatSpec { "test-history-1.ucl" should "verify all assertions." in { diff --git a/test/test-redundant-assign.ucl b/test/test-redundant-assign.ucl new file mode 100644 index 000000000..53b70b945 --- /dev/null +++ b/test/test-redundant-assign.ucl @@ -0,0 +1,31 @@ +module main { + + var index: integer; + var myArray: [integer]integer; + + procedure shouldPass() + modifies myArray, index; + { + index = 1; + myArray[index] = 0; + assert(myArray[1] == 0); // this assertion should pass + } + + procedure shouldPass2() + modifies myArray, index; + { + index = 1; + myArray[index] = 0; + assert(myArray[1] == 0); // this assertion should pass + index = 2; // assignment should be ignored + } + + + control { + v = verify(shouldPass); + v = verify(shouldPass2); + check; + print_results; + v.print_cex(index); + } +} From 2e1ffcc3a8fdf86954ced9639894b3d24b74f077 Mon Sep 17 00:00:00 2001 From: polgreen Date: Tue, 27 Oct 2020 14:59:09 -0700 Subject: [PATCH 057/119] remove parenthesis from file names --- src/main/scala/uclid/SymbolicSimulator.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/SymbolicSimulator.scala b/src/main/scala/uclid/SymbolicSimulator.scala index c09d0b53a..3a254e942 100644 --- a/src/main/scala/uclid/SymbolicSimulator.scala +++ b/src/main/scala/uclid/SymbolicSimulator.scala @@ -280,8 +280,8 @@ class SymbolicSimulator (module : Module) { val procName = cmd.args(0)._1.asInstanceOf[Identifier] val proc = module.procedures.find(p => p.id == procName).get val label : String = cmd.resultVar match { - case Some(l) => l.toString + ": verify(%s)".format(procName.toString()) - case None => "verify(%s)".format(procName.toString) + case Some(l) => l.toString + ": verify_%s".format(procName.toString()) + case None => "verify_%s".format(procName.toString) } verifyProcedure(proc, label) case "check" => { From c76ef1e8bbfa1cb06f42dd3a02138002bca938bc Mon Sep 17 00:00:00 2001 From: polgreen Date: Tue, 27 Oct 2020 15:12:03 -0700 Subject: [PATCH 058/119] pre-emptively check for array selects on LHS of primed var assign --- src/main/scala/uclid/lang/StatementScheduler.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/scala/uclid/lang/StatementScheduler.scala b/src/main/scala/uclid/lang/StatementScheduler.scala index eea0b16ee..6386f4500 100644 --- a/src/main/scala/uclid/lang/StatementScheduler.scala +++ b/src/main/scala/uclid/lang/StatementScheduler.scala @@ -247,7 +247,17 @@ object StatementScheduler { case AssertStmt(e, _) => primeReadSet(e) case AssumeStmt(e, _) => primeReadSet(e) case HavocStmt(_) => Set.empty - case AssignStmt(_, rhss) => primeReadSets(rhss) + case AssignStmt(lhss, rhss) => + // this code is not necessary because we do not support updating arrays using an array select e.g., A[i]' = 0 + // but it is added here incase we do support this in future + { + val arrayIndices = + lhss flatMap { + case arrayselect : LhsArraySelect => Some(arrayselect.indices) + case _ => None + } + primeReadSets(rhss)++primeReadSets(arrayIndices.flatten) + } case BlockStmt(vars, stmts) => val declaredVars : Set[Identifier] = vars.flatMap(vs => vs.ids.map(v => v)).toSet primeReadSets(stmts, context + vars) -- declaredVars From 828c3c4b02ddea06d1765a16802d64454cc90569 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 30 Oct 2020 21:45:43 +0530 Subject: [PATCH 059/119] added counting script for zkHAT - motivating example from CAV20 paper --- test/modelcounter/counting-zkhat.ucl | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/modelcounter/counting-zkhat.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl new file mode 100644 index 000000000..9b351dd0e --- /dev/null +++ b/test/modelcounter/counting-zkhat.ucl @@ -0,0 +1,41 @@ +// Model counting in ZK Hats - Motivating example in CAV 2020 paper + +module main { + + define V(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define Vf(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define V1(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: !Y[i]); + + define W(i : integer) : boolean = 0 <= i < 2; + + proof { + // Proof rules 2 - 7 from paper. + + // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) + assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == + #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + + #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); + + // 3. (ConstEq) #Y. V1(Y, R) = 1 + assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; + + // 4. (ConstEq) #Y. Vf(Y, 1) = 2 + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + + // 5. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (R: integer)] :: (W(i)) + skolems(Y[(R) -> (i == 1)]); + + // 6. (Range) #i. W(i) == 2 + assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; + + } +} From 7e6ce2958a8efdd609d7dbdbe4bfb5bd316c906f Mon Sep 17 00:00:00 2001 From: E Polgreen Date: Mon, 2 Nov 2020 16:36:11 -0800 Subject: [PATCH 060/119] update z3 version in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e2e89174..73d1e131c 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ There are currently two ways to install UCLID5: downloading the latest pre-build ## Pre-requisites -#### 1. [Z3 version 4.6.0.](https://github.com/Z3Prover/z3/releases/tag/z3-4.6.0) -You will need the Z3 SMT solver to be installed on your system. If you are building Z3 from source, make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). To install z3 on Unix-like systems, download the source code and run the following: +#### 1. [Z3 version 4.8.8.](https://github.com/Z3Prover/z3/releases/tag/z3-4.8.8) +You will need the Z3 SMT solver to be installed on your system. Earlier versions of Z3 should work, but the CI is tested with version 4.8.8. If you are building Z3 from source, make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). To install z3 on Unix-like systems, download the source code and run the following: ```bash python scripts/mk_make.py --java From 4d13e51f42a84945f7da1141ccc60ce0b632163d Mon Sep 17 00:00:00 2001 From: Yatin Manerkar Date: Thu, 5 Nov 2020 01:37:31 -0400 Subject: [PATCH 061/119] README: Improve clarity --- README.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 73d1e131c..d4d50a1c5 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,12 @@ Sanjit A. Seshia and Pramod Subramanyan. UCLID5: Integrating # Installation There are currently two ways to install UCLID5: downloading the latest pre-build package and building from source. Please make sure you have all the pre-requisites before proceeding to installation. Due to the nuances in the later Mac OS versions, we prepare separately a compact list of the installation instructions [here](mac-install.md). -## Pre-requisites +## Pre-requisites #### 1. [Z3 version 4.8.8.](https://github.com/Z3Prover/z3/releases/tag/z3-4.8.8) -You will need the Z3 SMT solver to be installed on your system. Earlier versions of Z3 should work, but the CI is tested with version 4.8.8. If you are building Z3 from source, make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). To install z3 on Unix-like systems, download the source code and run the following: +You will need the Z3 SMT solver to be installed on your system. Earlier versions of Z3 should work, but the CI is tested with version 4.8.8. uclid5 requires that the Z3 dynamic link library (libz3.so on Unix-like platforms) as well as the dynamic link library for the Z3/Java API (libz3java.so on Unix-like platforms) be in your dynamic library path (`$LD_LIBRARY_PATH` on Unix-like platforms; just `PATH` on Windows). + +If you prefer to build Z3 from source, make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). To install z3 on Unix-like systems from source, download the source code and run the following: ```bash python scripts/mk_make.py --java @@ -26,7 +28,7 @@ make sudo make install ``` -uclid5 requires that the Z3 dynamic link library (libz3.so on Unix-like platforms) as well as the dynamic link library for the Z3/Java API (libz3java.so on Unix-like platforms) be in your dynamic library path (`$LD_LIBRARY_PATH` on Unix-like platforms; just `PATH` on Windows). +Finally copy the jar file `path/to/z3/build/com.microsoft.z3.jar` to the dir `path/to/uclid5/lib/com.microsoft.z3.jar` **If you are using Mac OS X El Capitan or above**, System Integrity Protection is a feature introduced by Apple in OS X El Capitan; it prevents the modifications of system-owned files and directories by any process without a specific ‘entitlement’, even when executed by a root user or a user with root privileges. Since Java is a SIP protected executable, it ignores the user set DYLD_LIBRARY_PATH, which prevents the system from recognizing the Z3 Dynamic Library. @@ -40,9 +42,6 @@ https://github.com/Z3Prover/z3/issues/294 #### 2. [OpenJDK version 8](https://openjdk.java.net/) or [Oracle JDK version 8](https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html) **If you are using Mac OS X Mojave or above**, we recommend using Java 11 or earlier. We have found some issues related to the System Integrity Protection when using Catalina or Mojave and later versions of OpenJDK. -#### 3. [SBT version 1.0 or greater.](http://www.scala-sbt.org/1.0/docs/Setup.html) -If you intend to build from source, you need to install sbt. You can skip this step if you are using the pre-build binaries. Install instructions for sbt are available at http://www.scala-sbt.org/1.0/docs/Setup.html - ## Download Pre-Built Binaries Download the latest stable pre-built package from [releases tab](https://github.com/uclid-org/uclid/releases). @@ -51,23 +50,15 @@ Download the latest stable pre-built package from [releases tab](https://github. Or, you could clone this repository and build from source. If you run into problems here, don't forget you can always fall back on the pre-built binaries linked above. +### Pre-requisites +In addition to the prerequisites mentioned [above](#prereqs), please also note the following if compiling from source: -### Prerequisites -Before you begin, make sure you have the following installed and properly set up: - -#### 1. Z3 version 4.6.0. -Make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). +#### 1. Z3 Setup Script -**(Z3 configuration)** uclid5 requires that the Z3 dynamic link library (libz3.so on Unix-like platforms) as well as the dynamic link library for the Z3/Java API (libz3java.so on Unix-like platforms) be in your dynamic library path (`$LD_LIBRARY_PATH` on Unix-like platforms; just `PATH` on Windows). - -Finally copy the jar file `path/to/z3/build/com.microsoft.z3.jar` to the dir `path/to/uclid5/lib/com.microsoft.z3.jar` - -#### 2. OpenJDK version 8 or Oracle JDK version 8 - -#### 3. SBT version 1.0 or greater. - -Install instructions for sbt are available at http://www.scala-sbt.org/1.0/docs/Setup.html +The `get-z3-linux.sh` script in the source repository makes it easy to set up Z3 for use with uclid5. To use the script, simply run `source get-z3-linux.sh` from the root directory of the uclid5 source repository. This script will download Z3 binaries from GitHub and set up your `PATH` and `LD_LIBRARY_PATH` accordingly (it uses `setup-z3-linux.sh` to do the latter). You will need to rerun `setup-z3-linux.sh` (or the commands in it) each time you open a new bash shell, or you can simply source it from your `.profile` or `.bashrc`. +#### 2. [SBT version 1.0 or greater.](http://www.scala-sbt.org/1.0/docs/Setup.html) +If you intend to build from source, you need to install sbt. You can skip this step if you are using the pre-build binaries. Install instructions for sbt are available at http://www.scala-sbt.org/1.0/docs/Setup.html ### Compiling uclid5 From 791e283bf367ff877fef972eac17131ac1dc1865 Mon Sep 17 00:00:00 2001 From: Yatin Manerkar Date: Thu, 5 Nov 2020 20:25:18 -0400 Subject: [PATCH 062/119] README.md: Add hyperlinks to help people navigate README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d4d50a1c5..c2f5ed03e 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Sanjit A. Seshia and Pramod Subramanyan. UCLID5: Integrating *Proceedings of the 16th ACM-IEEE International Conference on Formal Methods and Models for System Design*. **(MEMOCODE 2018)**. Beijing, China. October 2018. # Installation -There are currently two ways to install UCLID5: downloading the latest pre-build package and building from source. Please make sure you have all the pre-requisites before proceeding to installation. Due to the nuances in the later Mac OS versions, we prepare separately a compact list of the installation instructions [here](mac-install.md). +There are currently two ways to install UCLID5: [downloading the latest pre-build package](#prebuilt) and [building from source](#srcbuild). Please make sure you have all the pre-requisites before proceeding to installation. Due to the nuances in the later Mac OS versions, we prepare separately a compact list of the installation instructions [here](mac-install.md). ## Pre-requisites #### 1. [Z3 version 4.8.8.](https://github.com/Z3Prover/z3/releases/tag/z3-4.8.8) -You will need the Z3 SMT solver to be installed on your system. Earlier versions of Z3 should work, but the CI is tested with version 4.8.8. uclid5 requires that the Z3 dynamic link library (libz3.so on Unix-like platforms) as well as the dynamic link library for the Z3/Java API (libz3java.so on Unix-like platforms) be in your dynamic library path (`$LD_LIBRARY_PATH` on Unix-like platforms; just `PATH` on Windows). +You will need the Z3 SMT solver to be installed on your system. Earlier versions of Z3 should work, but the CI is tested with version 4.8.8. uclid5 requires that the Z3 dynamic link library (libz3.so on Unix-like platforms) as well as the dynamic link library for the Z3/Java API (libz3java.so on Unix-like platforms) be in your dynamic library path (`$LD_LIBRARY_PATH` on Unix-like platforms; just `PATH` on Windows). If compiling from source, the source repository provides an easy way to set up Z3. See the instructions for [compiling from source](#srcbuild) for details. If you prefer to build Z3 from source, make sure the Z3/Java interface is enabled in your build (typically by passing `--java` to the `mk_make.py` script). To install z3 on Unix-like systems from source, download the source code and run the following: @@ -42,11 +42,11 @@ https://github.com/Z3Prover/z3/issues/294 #### 2. [OpenJDK version 8](https://openjdk.java.net/) or [Oracle JDK version 8](https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html) **If you are using Mac OS X Mojave or above**, we recommend using Java 11 or earlier. We have found some issues related to the System Integrity Protection when using Catalina or Mojave and later versions of OpenJDK. -## Download Pre-Built Binaries +## Download Pre-Built Binaries Download the latest stable pre-built package from [releases tab](https://github.com/uclid-org/uclid/releases). -## Building From Source +## Building From Source Or, you could clone this repository and build from source. If you run into problems here, don't forget you can always fall back on the pre-built binaries linked above. From 69199e1279bccd4cbb33d598eaab21381a7c7262 Mon Sep 17 00:00:00 2001 From: polgreen Date: Thu, 5 Nov 2020 17:19:40 -0800 Subject: [PATCH 063/119] add minimalist contribution guidelines --- CONTRIBUTING.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a9c36098d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing to UCLID5 + +UCLID5 is a relatively new tool and we welcome new contributors who want to get involved. Here are some very minimalistic guidelines on code and pull requests: + + +## CODE: +1. Every new function introduced should be documented, for example like this: +~~~ +/** + * Create new SMT symbolic variables for each state. + * + * This is called after each step of symbolic simulation and prevents the expression + * trees from blowing up. + * + * @param st The symbol table. + * @param step The current frame number. + * @param scope The current scope. + */ + def renameStates(st : SymbolTable, eqStates : Set[Identifier], frameNumber : Int, scope : Scope, addAssumption : (smt.Expr, List[ExprDecorator]) => Unit) : SymbolTable = { +~~~ + +2. Please don’t leave commented out code in the code base if you can avoid it. If it really must stay, add a comment to it saying why it must stay, what it was for and why it was commented out. + +## Pull requests: +1. Each new feature or bug fix should be done on a new branch +2. Create a separate PR for each new feature or bug fix. +3. Where possible break down large new features into separate PRs. +4. Every PR for a new feature should include a set of regression tests that fully test the feature +5. Every PR for a bug fix should include a regression test that tests the fix +6. When you get feedback on a PR, push the changes to the same branch so they appear in the same PR. +7. Write good commit messages: https://blogs.gnome.org/danni/2011/10/25/a-guide-to-writing-git-commit-messages/ +Every commit should have a meaningful commit message, which is less than 72 characters long. If you need more than that, the commit should have a short heading commit message followed by a longer description on a new line. +8. Tidy up your commit history before you PR: use interactive rebase to squash together messy commits into a single meaningful commit with a good commit message. e.g., these two commits should be squashed into one: +~~~ +commit c76ef1e8bbfa1cb06f42dsdd3g02138002bca938 +Author: polgreen +Date: Tue Oct 27 15:12:03 2020 -0700 + + fixes mistake in previous commit + +commit 6502265e4a85688ff92d963419c1957ef3cb73b5 +Author: polgreen +Date: Tue Oct 27 11:18:52 2020 -0700 + + Add array reads to readset + + The redundant assignment eliminator was removing relevant assigns because + it was failing to count array select statements as reads of the index variable, if the array select + happens on the LHS of an assign. Bug exposed by test/test-redundant-assign.ucl +~~~ +9. Preserve bisectability: every commit should compile. If your commit does not compile, use interactive rebase to fix it. +10. Don’t change whitespace unnecessarily. If you are going to change whitespace, then change it in a specific commit with a message that says “white space changes” +11. Don’t change the .gitignore unnecessarily. If you are going to change it, do it in a separate commit with a message that explains why the commit is necessary +12. Delete any branches on the main repo (uclid-org/uclid) as soon as the PR has been merged. If you want to keep branches, do it on your own fork. From 09fb91edfe294b2e1951a64a0bb1df8649266a91 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 16:31:36 +0530 Subject: [PATCH 064/119] Initial commit on branch modelcounting. --- src/main/scala/uclid/UclidMain.scala | 10 +++++++++- src/main/scala/uclid/lang/modelcounts/UMCMain.scala | 7 +++++++ src/main/scala/uclid/lang/modelcounts/UMCParser.scala | 5 +++++ test/modelcounter/hello.ucl | 6 ++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/uclid/lang/modelcounts/UMCMain.scala create mode 100644 src/main/scala/uclid/lang/modelcounts/UMCParser.scala create mode 100644 test/modelcounter/hello.ucl diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 7a44fac57..e914352de 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -78,7 +78,8 @@ object UclidMain { printStackTrace: Boolean = false, verbose : Int = 0, files : Seq[java.io.File] = Seq(), - testFixedpoint: Boolean = false + testFixedpoint: Boolean = false, + modelCounter: Boolean = false ) def parseOptions(args: Array[String]) : Option[Config] = { @@ -120,6 +121,10 @@ object UclidMain { opt[Unit]('t', "test-fixedpoint").action { (_, c) => c.copy(testFixedpoint = true) }.text("Test fixed point") + + opt[Unit]('C', "model-counter").action { + (_, c) => c.copy(modelCounter = true) + }.text("Model counter DSL.") help("help").text("prints this usage text") @@ -140,6 +145,9 @@ object UclidMain { smt.Z3HornSolver.test1() return } + if (config.modelCounter) { + config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + } val mainModuleName = Identifier(config.mainModuleName) val modules = compile(config.files, mainModuleName) val mainModule = instantiate(config, modules, mainModuleName, true) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala new file mode 100644 index 000000000..cfcb0767d --- /dev/null +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -0,0 +1,7 @@ +package uclid.lang.modelcounts + +object UMCMain { + def checkModel(f: java.io.File) { + println("Model counter invoked.") + } +} \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala new file mode 100644 index 000000000..9a17aa2eb --- /dev/null +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -0,0 +1,5 @@ +package uclid.lang.modelcounts + +class UMCParser { + +} \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl new file mode 100644 index 000000000..f52e0c535 --- /dev/null +++ b/test/modelcounter/hello.ucl @@ -0,0 +1,6 @@ +define f(n : integer) : boolean = n >= 5 && n <= 10; +define g(n : integer) : boolean = n >= 11 && n <= 20; +define h(n : integer) : boolean = n >= 5 && n <= 20; + +assert #SAT[n](f(n) && g(n)) < 1; + From 113c42dbae5b2a7549d4a85de07faa2e06006efb Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 18:00:08 +0530 Subject: [PATCH 065/119] starting to parse the model counter files. --- src/main/scala/uclid/lang/UclidLanguage.scala | 2 +- src/main/scala/uclid/lang/UclidParser.scala | 10 +++++- .../uclid/lang/modelcounts/UMCMain.scala | 3 +- .../uclid/lang/modelcounts/UMCParser.scala | 34 +++++++++++++++++-- test/modelcounter/hello.ucl | 11 +++--- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 75498a9a4..b7cc01d3d 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -135,7 +135,7 @@ object ASTNode { /** All elements in the AST are derived from this class. * The plan is to stick an ID into this later so that we can use the ID to store auxiliary information. */ -sealed trait ASTNode extends Positional with PositionedNode { +trait ASTNode extends Positional with PositionedNode { val astNodeId = IdGenerator.newId() } diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 9b8d53249..884f9410e 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -83,7 +83,7 @@ trait UclidTokenParsers extends TokenParsers { elem("identifier", _.isInstanceOf[Identifier]) ^^ (_.chars) } -object UclidParser extends UclidTokenParsers with PackratParsers { +class UclidParser extends UclidTokenParsers with PackratParsers { type Tokens = UclidTokens val lexical = new UclidLexical @@ -804,4 +804,12 @@ object UclidParser extends UclidTokenParsers with PackratParsers { case NoSuccess(msg, next) => throw new Utils.SyntaxError(msg, Some(next.pos), Some(filename)) } } +} + +object UclidParser { + val parserObj = new UclidParser() + def parseModel(filename: String, text: String): List[Module] = { + parserObj.parseModel(filename, text) } +} + \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index cfcb0767d..7f92db412 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -2,6 +2,7 @@ package uclid.lang.modelcounts object UMCMain { def checkModel(f: java.io.File) { - println("Model counter invoked.") + val module = UMCParser.parseUMCModel(f) + println(module.toString()) } } \ No newline at end of file diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 9a17aa2eb..659fffc5d 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,5 +1,35 @@ package uclid.lang.modelcounts -class UMCParser { - +import uclid.lang.{Decl, Module, UclidParser} + +class UMCParser extends UclidParser { + lazy val UMCDecl: PackratParser[Decl] = + positioned (TypeDecl | DefineDecl) + + lazy val UMCModule: PackratParser[Module] = positioned { + KwModule ~> Id ~ ("{" ~> rep(UMCDecl) <~ "}") ^^ { + case id ~ decls => uclid.lang.Module(id, decls, List.empty, List.empty) + } + } + + def parseUMCModel(filename : String, text: String): Module = { + val tokens = new PackratReader(new lexical.Scanner(text)) + phrase(UMCModule)(tokens) match { + case Success(module, _) => module + case NoSuccess(msg, next) => throw new uclid.Utils.SyntaxError(msg, Some(next.pos), Some(filename)) + } + } +} + +object UMCParser { + val parserObj = new UMCParser() + val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) + def parseUMCModel(file: java.io.File) : Module = { + val filePath = file.getPath() + val text = scala.io.Source.fromFile(filePath).mkString + filenameAdderPass.setFilename(filePath) + filenameAdderPass.visit( + parserObj.parseUMCModel(filePath, text), + uclid.lang.Scope.empty).get + } } \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index f52e0c535..68232a0b1 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,6 +1,9 @@ -define f(n : integer) : boolean = n >= 5 && n <= 10; -define g(n : integer) : boolean = n >= 11 && n <= 20; -define h(n : integer) : boolean = n >= 5 && n <= 20; +module main { -assert #SAT[n](f(n) && g(n)) < 1; + define f(n : integer) : boolean = n >= 5 && n <= 10; + define g(n : integer) : boolean = n >= 11 && n <= 20; + define h(n : integer) : boolean = n >= 5 && n <= 20; + + // assert #SAT[n](f(n) && g(n)) < 1; +} From 539ef60ab4fae06380bdbb4c6195fee236d31669 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 28 Oct 2019 23:09:42 +0530 Subject: [PATCH 066/119] parser is beginning to work. --- src/main/scala/uclid/UclidMain.scala | 1 + .../uclid/lang/modelcounts/UMCParser.scala | 31 ++++++++++++++----- test/modelcounter/hello.ucl | 4 ++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index e914352de..2dc403a0a 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -147,6 +147,7 @@ object UclidMain { } if (config.modelCounter) { config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + return } val mainModuleName = Identifier(config.mainModuleName) val modules = compile(config.files, mainModuleName) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 659fffc5d..365bbba92 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,18 +1,33 @@ package uclid.lang.modelcounts -import uclid.lang.{Decl, Module, UclidParser} +import uclid.{lang => l} -class UMCParser extends UclidParser { - lazy val UMCDecl: PackratParser[Decl] = + +class UMCParser extends l.UclidParser { + lazy val KwProof = "proof" + lexical.reserved += (KwProof) + + lazy val UMCDecl: PackratParser[uclid.lang.Decl] = positioned (TypeDecl | DefineDecl) - lazy val UMCModule: PackratParser[Module] = positioned { - KwModule ~> Id ~ ("{" ~> rep(UMCDecl) <~ "}") ^^ { - case id ~ decls => uclid.lang.Module(id, decls, List.empty, List.empty) + lazy val AssertStmt: PackratParser[uclid.lang.Statement] = positioned { + KwAssert ~> Expr <~ ";" ^^ { case e => l.AssertStmt(e, None) } + } + lazy val ProofStmt: PackratParser[l.Statement] = + positioned ( AssertStmt ); + lazy val ProofScript: PackratParser[List[l.Statement]] = { + KwProof ~ "{" ~> rep(ProofStmt) <~ "}" + } + lazy val UMCModule: PackratParser[l.Module] = positioned { + KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { + case id ~ decls ~ proof => { + val next = l.NextDecl(l.BlockStmt(List.empty, proof)) + uclid.lang.Module(id, next :: decls, List.empty, List.empty) + } } } - def parseUMCModel(filename : String, text: String): Module = { + def parseUMCModel(filename : String, text: String): l.Module = { val tokens = new PackratReader(new lexical.Scanner(text)) phrase(UMCModule)(tokens) match { case Success(module, _) => module @@ -24,7 +39,7 @@ class UMCParser extends UclidParser { object UMCParser { val parserObj = new UMCParser() val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) - def parseUMCModel(file: java.io.File) : Module = { + def parseUMCModel(file: java.io.File) : l.Module = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString filenameAdderPass.setFilename(filePath) diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 68232a0b1..f26a81595 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -4,6 +4,8 @@ module main { define g(n : integer) : boolean = n >= 11 && n <= 20; define h(n : integer) : boolean = n >= 5 && n <= 20; - // assert #SAT[n](f(n) && g(n)) < 1; + proof { + assert h(n) <==> (f(n) || g(n)); + } } From 90e5ec573dfd5a3de44bd286c53f0b51d3ecc441 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 4 Nov 2019 05:03:08 +0530 Subject: [PATCH 067/119] some more progress on the model counter. --- src/main/scala/uclid/UclidMain.scala | 2 +- .../uclid/lang/modelcounts/UMCMain.scala | 50 +++++++++++++++++-- test/modelcounter/hello.ucl | 2 +- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 2dc403a0a..b6e7a9099 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -146,7 +146,7 @@ object UclidMain { return } if (config.modelCounter) { - config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f)) + config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f, config)) return } val mainModuleName = Identifier(config.mainModuleName) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index 7f92db412..3ae364b0d 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -1,8 +1,52 @@ package uclid.lang.modelcounts +import uclid.UclidMain +import uclid.{lang => l} + object UMCMain { - def checkModel(f: java.io.File) { - val module = UMCParser.parseUMCModel(f) - println(module.toString()) + def checkModel(f: java.io.File, config: UclidMain.Config) { + val passManager = createModulePassManager(l.Identifier(config.mainModuleName)) + passManager.run(UMCParser.parseUMCModel(f), l.Scope.empty) match { + case Some(m) => UclidMain.execute(m, config) + case None => + throw new uclid.Utils.ParserError( + "Unable to find main module: " + config.mainModuleName, None, None) + } } + def createModulePassManager(mainModuleName: l.Identifier) = { + val passManager = new l.PassManager("compile") + // passManager.addPass(new ASTPrinter()) + passManager.addPass(new l.ModuleCanonicalizer()) + passManager.addPass(new l.ModuleTypesImportCollector()) + passManager.addPass(new l.ModuleConstantsImportCollector()) + passManager.addPass(new l.ModuleFunctionsImportCollector()) + + passManager.addPass(new l.ExternalTypeAnalysis()) + passManager.addPass(new l.ExternalTypeRewriter()) + passManager.addPass(new l.FuncExprRewriter()) + passManager.addPass(new l.InstanceModuleNameChecker()) + passManager.addPass(new l.InstanceModuleTypeRewriter()) + passManager.addPass(new l.RewritePolymorphicSelect()) + passManager.addPass(new l.ConstantLitRewriter()) + passManager.addPass(new l.TypeSynonymFinder()) + passManager.addPass(new l.TypeSynonymRewriter()) + passManager.addPass(new l.BlockVariableRenamer()) + passManager.addPass(new l.BitVectorSliceFindWidth()) + passManager.addPass(new l.ExpressionTypeChecker()) + passManager.addPass(new l.VerificationExpressionChecker()) + passManager.addPass(new l.PolymorphicTypeRewriter()) + passManager.addPass(new l.FindProcedureDependency()) + passManager.addPass(new l.DefDepGraphChecker()) + passManager.addPass(new l.RewriteDefines()) + passManager.addPass(new l.ModuleTypeChecker()) + passManager.addPass(new l.SemanticAnalyzer()) + passManager.addPass(new l.ControlCommandChecker()) + passManager.addPass(new l.ComputeInstanceTypes()) + passManager.addPass(new l.ModuleInstanceChecker()) + passManager.addPass(new l.BlockFlattener()) + passManager.addPass(new l.ModuleCleaner(mainModuleName)) + passManager.addPass(new l.BlockVariableRenamer()) + passManager + } + } \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index f26a81595..df415a287 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert h(n) <==> (f(n) || g(n)); + assert (forall (n : integer) :: h(n) <==> (f(n) || g(n))); } } From 33d0d4b94149b26034edacbb343a5a8248d7b156 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 4 Nov 2019 16:15:50 +0530 Subject: [PATCH 068/119] first baby proof is working. --- .../uclid/lang/modelcounts/UMCParser.scala | 36 +++++++++++++++---- test/modelcounter/hello.ucl | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala index 365bbba92..c6b27f68f 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCParser.scala @@ -1,28 +1,50 @@ package uclid.lang.modelcounts import uclid.{lang => l} +import uclid.lang.Identifier +import uclid.smt.IntLit class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lexical.reserved += (KwProof) + lazy val ControlBlock : List[l.GenericProofCommand] = List( + l.GenericProofCommand( + l.Identifier("unroll"), + List.empty, List((l.IntLit(1), "1")), + Some(l.Identifier("v")), None), + l.GenericProofCommand( + l.Identifier("check"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_results"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_cex"), + List.empty, List.empty, + None, Some(l.Identifier("v"))) + ) + lazy val UMCDecl: PackratParser[uclid.lang.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertStmt: PackratParser[uclid.lang.Statement] = positioned { - KwAssert ~> Expr <~ ";" ^^ { case e => l.AssertStmt(e, None) } + lazy val AssertDecl: PackratParser[uclid.lang.SpecDecl] = positioned { + KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { + case id ~ e => l.SpecDecl(id, e, List.empty) + } } - lazy val ProofStmt: PackratParser[l.Statement] = - positioned ( AssertStmt ); - lazy val ProofScript: PackratParser[List[l.Statement]] = { + lazy val ProofStmt: PackratParser[l.Decl] = + positioned ( AssertDecl ); + lazy val ProofScript: PackratParser[List[l.Decl]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - val next = l.NextDecl(l.BlockStmt(List.empty, proof)) - uclid.lang.Module(id, next :: decls, List.empty, List.empty) + uclid.lang.Module(id, decls ++ proof, ControlBlock, List.empty) } } } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index df415a287..4993789ee 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert (forall (n : integer) :: h(n) <==> (f(n) || g(n))); + assert or: (forall (n : integer) :: h(n) <==> (f(n) || g(n))); } } From 60a2aaeb6e5705964644926e292a5af675bc3eb9 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Tue, 5 Nov 2019 08:58:18 +0530 Subject: [PATCH 069/119] intermediate commit. --- src/main/scala/uclid/lang/UclidLanguage.scala | 8 ++++++++ src/main/scala/uclid/lang/UclidParser.scala | 7 ++++++- test/modelcounter/hello.ucl | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index b7cc01d3d..858c3ac0d 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -449,6 +449,14 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } +case class CountingOp(vs: List[(Identifier, Type)]) extends QuantifiedBooleanOperator { + override def variables = vs + override def toString() = "#[" + + Utils.join(vs.map(_.toString()), ", ") + "]" + override def fixity = Operator.PREFIX + override val hashId = 1402 + override val md5hashCode = computeMD5Hash(vs) +} // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 884f9410e..5d22b0da0 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -180,7 +180,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { // lazy val TemporalOpWUntil = "W" // lazy val TemporalOpRelease = "R" - lexical.delimiters ++= List("(", ")", ",", "[", "]", + lexical.delimiters ++= List("(", ")", ",", "[", "]", "#[", "bv", "{", "}", ";", "=", ":", "::", ".", "*", "::=", "->", OpAnd, OpOr, OpBvAnd, OpBvOr, OpBvXor, OpBvNot, OpAdd, OpSub, OpMul, OpBiImpl, OpImpl, OpLT, OpGT, OpLE, OpGE, OpULT, OpUGT, OpULE, OpUGE, @@ -373,6 +373,11 @@ class UclidParser extends UclidTokenParsers with PackratParsers { } | ConstArray | KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | + "#[" ~> IdTypeList ~ ( "]" ~ ":" ~ "(" ~> Expr <~ ")" ) ^^ { + case ids ~ expr => { + OperatorApplication(lang.CountingOp(ids), List(expr)) + } + } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | Id diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 4993789ee..7186ad025 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,7 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: (forall (n : integer) :: h(n) <==> (f(n) || g(n))); + assert or: #[n]:(f(n)) == #[n]:(g(n)) + #[n]:(h(n)); } } From 09727b70ccc6e93491eae36cd6300e371d31739d Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Tue, 7 Apr 2020 23:48:21 +0530 Subject: [PATCH 070/119] Merge with master after all recent commits. --- .../uclid/lang/modelcounts/UMCMain.scala | 47 ++----------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala index 3ae364b0d..939ba78fd 100644 --- a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/lang/modelcounts/UMCMain.scala @@ -5,48 +5,7 @@ import uclid.{lang => l} object UMCMain { def checkModel(f: java.io.File, config: UclidMain.Config) { - val passManager = createModulePassManager(l.Identifier(config.mainModuleName)) - passManager.run(UMCParser.parseUMCModel(f), l.Scope.empty) match { - case Some(m) => UclidMain.execute(m, config) - case None => - throw new uclid.Utils.ParserError( - "Unable to find main module: " + config.mainModuleName, None, None) - } + val module = UMCParser.parseUMCModel(f) + println("Managed to parse module: " + module.id.toString()) } - def createModulePassManager(mainModuleName: l.Identifier) = { - val passManager = new l.PassManager("compile") - // passManager.addPass(new ASTPrinter()) - passManager.addPass(new l.ModuleCanonicalizer()) - passManager.addPass(new l.ModuleTypesImportCollector()) - passManager.addPass(new l.ModuleConstantsImportCollector()) - passManager.addPass(new l.ModuleFunctionsImportCollector()) - - passManager.addPass(new l.ExternalTypeAnalysis()) - passManager.addPass(new l.ExternalTypeRewriter()) - passManager.addPass(new l.FuncExprRewriter()) - passManager.addPass(new l.InstanceModuleNameChecker()) - passManager.addPass(new l.InstanceModuleTypeRewriter()) - passManager.addPass(new l.RewritePolymorphicSelect()) - passManager.addPass(new l.ConstantLitRewriter()) - passManager.addPass(new l.TypeSynonymFinder()) - passManager.addPass(new l.TypeSynonymRewriter()) - passManager.addPass(new l.BlockVariableRenamer()) - passManager.addPass(new l.BitVectorSliceFindWidth()) - passManager.addPass(new l.ExpressionTypeChecker()) - passManager.addPass(new l.VerificationExpressionChecker()) - passManager.addPass(new l.PolymorphicTypeRewriter()) - passManager.addPass(new l.FindProcedureDependency()) - passManager.addPass(new l.DefDepGraphChecker()) - passManager.addPass(new l.RewriteDefines()) - passManager.addPass(new l.ModuleTypeChecker()) - passManager.addPass(new l.SemanticAnalyzer()) - passManager.addPass(new l.ControlCommandChecker()) - passManager.addPass(new l.ComputeInstanceTypes()) - passManager.addPass(new l.ModuleInstanceChecker()) - passManager.addPass(new l.BlockFlattener()) - passManager.addPass(new l.ModuleCleaner(mainModuleName)) - passManager.addPass(new l.BlockVariableRenamer()) - passManager - } - -} \ No newline at end of file +} From e8ba396d213a855727fb8f1f924420bb9026b216 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 8 Apr 2020 00:10:36 +0530 Subject: [PATCH 071/119] Moving files for modelcounter into extensions. --- .../scala/uclid/{lang => extensions}/modelcounts/UMCMain.scala | 0 .../scala/uclid/{lang => extensions}/modelcounts/UMCParser.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/main/scala/uclid/{lang => extensions}/modelcounts/UMCMain.scala (100%) rename src/main/scala/uclid/{lang => extensions}/modelcounts/UMCParser.scala (100%) diff --git a/src/main/scala/uclid/lang/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala similarity index 100% rename from src/main/scala/uclid/lang/modelcounts/UMCMain.scala rename to src/main/scala/uclid/extensions/modelcounts/UMCMain.scala diff --git a/src/main/scala/uclid/lang/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala similarity index 100% rename from src/main/scala/uclid/lang/modelcounts/UMCParser.scala rename to src/main/scala/uclid/extensions/modelcounts/UMCParser.scala From f384992faf75f99eaaf56d540546698263679b6b Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 8 Apr 2020 01:08:29 +0530 Subject: [PATCH 072/119] The toy file is being parsed. --- .../extensions/modelcounts/UMCMain.scala | 38 ++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 51 ++++++++++++++++--- src/main/scala/uclid/lang/UclidLanguage.scala | 7 ++- src/main/scala/uclid/lang/UclidParser.scala | 10 ++-- test/modelcounter/hello.ucl | 3 +- 5 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 939ba78fd..ea3609b31 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -1,3 +1,41 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Main file for the UCLID model counter. + * + */ package uclid.lang.modelcounts import uclid.UclidMain diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index c6b27f68f..18022fabd 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -1,3 +1,42 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Parser for the UCLID model counter. + * + */ + package uclid.lang.modelcounts import uclid.{lang => l} @@ -28,10 +67,10 @@ class UMCParser extends l.UclidParser { None, Some(l.Identifier("v"))) ) - lazy val UMCDecl: PackratParser[uclid.lang.Decl] = + lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertDecl: PackratParser[uclid.lang.SpecDecl] = positioned { + lazy val AssertDecl: PackratParser[l.SpecDecl] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { case id ~ e => l.SpecDecl(id, e, List.empty) } @@ -44,7 +83,7 @@ class UMCParser extends l.UclidParser { lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - uclid.lang.Module(id, decls ++ proof, ControlBlock, List.empty) + l.Module(id, decls ++ proof, ControlBlock, List.empty) } } } @@ -60,13 +99,13 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() - val filenameAdderPass = new uclid.lang.AddFilenameRewriter(None) + val filenameAdderPass = new l.AddFilenameRewriter(None) def parseUMCModel(file: java.io.File) : l.Module = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString filenameAdderPass.setFilename(filePath) filenameAdderPass.visit( parserObj.parseUMCModel(filePath, text), - uclid.lang.Scope.empty).get + l.Scope.empty).get } -} \ No newline at end of file +} diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 858c3ac0d..c425bddfb 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -159,7 +159,7 @@ object Operator { def oldInstance(c : OperatorApplication) = OperatorApplication(OldOperator(), List(c)) def history(c : Identifier, e : Expr) = OperatorApplication(HistoryOperator(), List(c, e)) } -sealed trait Operator extends ASTNode { +trait Operator extends ASTNode { def fixity : Int def isPolymorphic = false def isTemporal = false @@ -449,15 +449,14 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } -case class CountingOp(vs: List[(Identifier, Type)]) extends QuantifiedBooleanOperator { - override def variables = vs +/** CountingOp is used in the model counting extension. */ +case class CountingOp(vs: List[(Identifier, Type)]) extends Operator { override def toString() = "#[" + Utils.join(vs.map(_.toString()), ", ") + "]" override def fixity = Operator.PREFIX override val hashId = 1402 override val md5hashCode = computeMD5Hash(vs) } - // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { override def fixity = Operator.INFIX diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 5d22b0da0..988a5e3a9 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -373,13 +373,11 @@ class UclidParser extends UclidTokenParsers with PackratParsers { } | ConstArray | KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | - "#[" ~> IdTypeList ~ ( "]" ~ ":" ~ "(" ~> Expr <~ ")" ) ^^ { - case ids ~ expr => { - OperatorApplication(lang.CountingOp(ids), List(expr)) - } - } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | + ("#[" ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { + case vars ~ e => OperatorApplication(CountingOp(vars), List(e)) + } | Id } @@ -817,4 +815,4 @@ object UclidParser { parserObj.parseModel(filename, text) } } - \ No newline at end of file + diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 7186ad025..d145a6c70 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,7 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[n]:(f(n)) == #[n]:(g(n)) + #[n]:(h(n)); + assert or: #[(n:integer) :: f(n)] == + #[(n:integer) :: g(n)] + #[(n:integer) :: h(n)]; } } From 0db408e8d04af78046b796cd8ff7d0ca8300abde Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Fri, 10 Apr 2020 01:34:44 +0530 Subject: [PATCH 073/119] New syntax for counting op + rewriting infra setup. --- src/main/scala/uclid/Hashable.scala | 44 ++++-- src/main/scala/uclid/UclidMain.scala | 2 +- .../extensions/modelcounts/UMCMain.scala | 8 +- .../extensions/modelcounts/UMCParser.scala | 20 ++- .../extensions/modelcounts/UMCRewriter.scala | 141 ++++++++++++++++++ src/main/scala/uclid/lang/UclidLanguage.scala | 13 +- src/main/scala/uclid/lang/UclidParser.scala | 4 +- test/modelcounter/hello.ucl | 4 +- 8 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala diff --git a/src/main/scala/uclid/Hashable.scala b/src/main/scala/uclid/Hashable.scala index 908e13c39..f527fe364 100644 --- a/src/main/scala/uclid/Hashable.scala +++ b/src/main/scala/uclid/Hashable.scala @@ -22,19 +22,43 @@ trait Hashable { md5.reset() md5.update(intToBytes(hashBaseId)) md5.update(intToBytes(hashId)) + md5.update(intToBytes(args.size)) // Recursively compute the md5 hash def computeMD5HashR(a : Any) : Unit = { a match { - case None => computeMD5HashR(0) - case Some(opt) => computeMD5HashR(opt) - case integer : Int => md5.update(intToBytes(integer)) - case str : String => md5.update(str.getBytes("UTF-8")) - case bigint : BigInt => md5.update(bigint.toByteArray) - case bool : Boolean => md5.update(if (bool) intToBytes(1) else intToBytes(0)) - case tuple2 : (Any, Any) => { computeMD5HashR(tuple2._1); computeMD5HashR(tuple2._2); } - case tuple3 : (Any, Any, Any) => { computeMD5HashR(tuple3._1); computeMD5HashR(tuple3._2); computeMD5HashR(tuple3._3) } - case lista : List[Any] => lista.foreach(e => computeMD5HashR(e)) - case hash : Hashable => md5.update(hash.md5hashCode) + case None => + md5.update(intToBytes(111)) + computeMD5HashR(0) + case Some(opt) => + md5.update(intToBytes(112)) + computeMD5HashR(opt) + case integer : Int => + md5.update(intToBytes(113)) + md5.update(intToBytes(integer)) + case str : String => + md5.update(intToBytes(114)) + md5.update(str.getBytes("UTF-8")) + case bigint : BigInt => + md5.update(intToBytes(115)) + md5.update(bigint.toByteArray) + case bool : Boolean => + md5.update(intToBytes(116)) + md5.update(if (bool) intToBytes(1) else intToBytes(0)) + case tuple2 : (Any, Any) => + md5.update(intToBytes(117)) + computeMD5HashR(tuple2._1) + computeMD5HashR(tuple2._2) + case tuple3 : (Any, Any, Any) => + md5.update(intToBytes(118)) + computeMD5HashR(tuple3._1) + computeMD5HashR(tuple3._2) + computeMD5HashR(tuple3._3) + case lista : List[Any] => + md5.update(intToBytes(119)) + md5.update(intToBytes(lista.size)) + lista.foreach(e => computeMD5HashR(e)) + case hash : Hashable => + md5.update(hash.md5hashCode) case _ => { UclidMain.println("Can't convert: " + a.getClass().toString()) throw new Utils.RuntimeError("Should not get here; Missing case!" + a.getClass().toString()) diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index b6e7a9099..65fd275fc 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -146,7 +146,7 @@ object UclidMain { return } if (config.modelCounter) { - config.files.foreach(f => lang.modelcounts.UMCMain.checkModel(f, config)) + config.files.foreach(f => extensions.modelcounts.UMCMain.checkModel(f, config)) return } val mainModuleName = Identifier(config.mainModuleName) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index ea3609b31..7be5237ba 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -9,7 +9,6 @@ * modification, are permitted provided that the following conditions are * met: * 1. Redistributions of source code must retain the above copyright notice, - * * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the @@ -36,14 +35,17 @@ * Main file for the UCLID model counter. * */ -package uclid.lang.modelcounts +package uclid.extensions.modelcounts import uclid.UclidMain import uclid.{lang => l} + object UMCMain { def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) - println("Managed to parse module: " + module.id.toString()) + println("Parsed module: " + module.id.toString()) + println(module.toString()) + new UMCRewriter(module).process() } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 18022fabd..52ddc30c0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -37,7 +37,7 @@ * */ -package uclid.lang.modelcounts +package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.lang.Identifier @@ -70,20 +70,26 @@ class UMCParser extends l.UclidParser { lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertDecl: PackratParser[l.SpecDecl] = positioned { + lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.SpecDecl(id, e, List.empty) + case id ~ e => l.AssertStmt(e, Some(id)) } } - lazy val ProofStmt: PackratParser[l.Decl] = - positioned ( AssertDecl ); - lazy val ProofScript: PackratParser[List[l.Decl]] = { + lazy val ProofStmt: PackratParser[l.AssertStmt] = + positioned ( AssertStmt ); + lazy val ProofScript: PackratParser[List[l.AssertStmt]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } lazy val UMCModule: PackratParser[l.Module] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - l.Module(id, decls ++ proof, ControlBlock, List.empty) + val proc = l.ProcedureDecl( + l.Identifier("countingProof"), // procedure name + l.ProcedureSig(List.empty, List.empty), // signature + l.BlockStmt(List.empty, proof), // body + List.empty, List.empty, Set.empty, // requires, ensures, modifies + l.ProcedureAnnotations(Set.empty)) // no annotations. + l.Module(id, decls ++ List(proc) , ControlBlock, List.empty) } } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala new file mode 100644 index 000000000..2a5037d47 --- /dev/null +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -0,0 +1,141 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Rewriter for the UCLID5 model counter. + * + */ +package uclid.extensions.modelcounts + +import uclid.{lang => l} +import uclid.Memo + +import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} + +class UMCRewriter(module : l.Module) { + val proofProcedure = module.procedures(0) + + /** Identify counting ops in a sequence of expressions. + * + * Note the recursion is to identifyCountOps which is a Memo. + */ + def _identifyCountOps(es : Seq[l.Expr]) : Set[l.OperatorApplication] = { + es.foldLeft(Set.empty[l.OperatorApplication]) { + (acc, e) => acc ++ identifyCountOps(e) + } + } + /** Identify counting ops in an expression. + * + * Note recursion is to identifyCountsOp which is a memo. + */ + def _identifyCountOps(e : l.Expr) : Set[l.OperatorApplication] = { + e match { + case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => + Set.empty + case l.ConstArray(e, typ) => + identifyCountOps(e) + case l.Tuple(es) => + _identifyCountOps(es) + case opapp : l.OperatorApplication => + val init : Set[l.OperatorApplication] = opapp.op match { + case l.CountingOp(_, _) => Set(opapp) + case _ => Set.empty + } + init ++ _identifyCountOps(opapp.operands) + case l.FuncApplication(e, args) => + identifyCountOps(e) ++ _identifyCountOps(args) + case l.Lambda(ids, e) => + identifyCountOps(e) + } + } + + /** + * Memoizing wrapper for finding all counting operators. + */ + val identifyCountOps = new Memo[l.Expr, Set[l.OperatorApplication]](_identifyCountOps _) + + /** Finding all the counting operators in a list of assert statements. */ + def identifyCountOps(proofBlk: List[l.AssertStmt]) : Set[l.OperatorApplication] = { + proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { + (acc, st) => acc ++ identifyCountOps(st.e) + } + } + + /** Identifiers that are already declared in the module. */ + val existingIds = module.decls.map(d => d.declNames).flatten.toSet + /** Identifiers that are declared + newly generated names. */ + val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds + /** Counters that track (roughly) the number of generated identifiers with each prefix. */ + val counters = MutableMap.empty[String, Int] + /** Generate a new id. */ + def generateId(prefix: String) : l.Identifier = { + var cnt = counters.get(prefix) match { + case Some(n) => n + 1 + case None => 1 + } + def getName(n : Int) = l.Identifier(prefix + "_" + n.toString()) + var name = getName(cnt) + while (usedIds.contains(name)) { + cnt += 1 + name = getName(cnt) + } + usedIds += name + counters.put(prefix, cnt) + name + } + + /** Generate UF decls for the identified counting operators. + * + */ + def generateUFDecls(ops : Set[l.OperatorApplication]) : (Map[l.OperatorApplication, l.FunctionDecl]) = { + ops.map { + opapp => { + assert (opapp.op.isInstanceOf[l.CountingOp]) + val op = opapp.op.asInstanceOf[l.CountingOp] + val ufId = generateId("count") + val sig = l.FunctionSig(op.ys, l.IntegerType()) + val uf = l.FunctionDecl(ufId, sig) + opapp -> uf + } + }.toMap + } + + def process() : l.Module = { + val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) + val countingOps = identifyCountOps(proofProcBody) + val ufDecls = generateUFDecls(countingOps) + println(ufDecls.toString()) + module + } +} \ No newline at end of file diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index c425bddfb..8701b9646 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -450,12 +450,15 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val md5hashCode = computeMD5Hash(vs, patterns) } /** CountingOp is used in the model counting extension. */ -case class CountingOp(vs: List[(Identifier, Type)]) extends Operator { - override def toString() = "#[" + - Utils.join(vs.map(_.toString()), ", ") + "]" +case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)]) extends Operator { + override def toString() = { + val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + "#[(" + s1 + ") for (" + s2 + ") :: " + } override def fixity = Operator.PREFIX override val hashId = 1402 - override val md5hashCode = computeMD5Hash(vs) + override val md5hashCode = computeMD5Hash(xs, ys) } // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { @@ -753,6 +756,8 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString + case CountingOp(_, _) => + op.toString() + operands(0).toString() + "]" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 988a5e3a9..1b27d7e19 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -375,8 +375,8 @@ class UclidParser extends UclidTokenParsers with PackratParsers { KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | - ("#[" ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { - case vars ~ e => OperatorApplication(CountingOp(vars), List(e)) + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { + case xs ~ ys ~ e => OperatorApplication(CountingOp(xs, ys), List(e)) } | Id } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index d145a6c70..e4e63fd52 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,8 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[(n:integer) :: f(n)] == - #[(n:integer) :: g(n)] + #[(n:integer) :: h(n)]; + assert or: #[(n:integer) for () :: f(n)] == + #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; } } From 104d41959e4dffcc8ad6a6d27287ead4a7528e50 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 00:21:57 +0530 Subject: [PATCH 074/119] Adding a parameter list to assert statements --- src/main/scala/uclid/SymbolicSimulator.scala | 4 +- .../extensions/modelcounts/UMCMain.scala | 3 +- .../extensions/modelcounts/UMCParser.scala | 2 +- .../extensions/modelcounts/UMCRewriter.scala | 50 ++++++++++++++++--- src/main/scala/uclid/lang/ASTVistors.scala | 4 +- .../uclid/lang/ModularProductProgram.scala | 13 +++-- .../scala/uclid/lang/ModuleFlattener.scala | 4 +- src/main/scala/uclid/lang/UclidLanguage.scala | 16 ++++-- test/modelcounter/hello.ucl | 4 +- 9 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/main/scala/uclid/SymbolicSimulator.scala b/src/main/scala/uclid/SymbolicSimulator.scala index 3a254e942..187163d2b 100644 --- a/src/main/scala/uclid/SymbolicSimulator.scala +++ b/src/main/scala/uclid/SymbolicSimulator.scala @@ -1741,7 +1741,7 @@ class SymbolicSimulator (module : Module) { frameLog.debug("symbolTable: %s".format(symbolTable.toString())) s match { case SkipStmt() => return symbolTable - case AssertStmt(e, id) => + case AssertStmt(e, id, params) => val frameTableP = frameTable.clone() frameTableP += symbolTable val simTable = ArrayBuffer(frameTableP) @@ -1829,7 +1829,7 @@ class SymbolicSimulator (module : Module) { def writeSet(stmt: Statement) : Set[Identifier] = stmt match { case SkipStmt() => Set.empty - case AssertStmt(e, id) => Set.empty + case AssertStmt(e, id, params) => Set.empty case AssumeStmt(e, id) => Set.empty case HavocStmt(h) => h match { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 7be5237ba..5f29be439 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -46,6 +46,7 @@ object UMCMain { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) println(module.toString()) - new UMCRewriter(module).process() + val moduleP = new UMCRewriter(module).process() + println(moduleP.toString()) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 52ddc30c0..fe8f879af 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -72,7 +72,7 @@ class UMCParser extends l.UclidParser { lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.AssertStmt(e, Some(id)) + case id ~ e => l.AssertStmt(e, Some(id), List.empty) } } lazy val ProofStmt: PackratParser[l.AssertStmt] = diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2a5037d47..7a16cdb30 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -45,12 +45,18 @@ import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} class UMCRewriter(module : l.Module) { val proofProcedure = module.procedures(0) + + /* We will be using this set in a number of places. */ + type CountingOpSet = Set[l.OperatorApplication] + /* A map from counting ops to the UFs that represent them. */ + type UFMap = Map[l.OperatorApplication, l.FunctionDecl] + /** Identify counting ops in a sequence of expressions. * * Note the recursion is to identifyCountOps which is a Memo. */ - def _identifyCountOps(es : Seq[l.Expr]) : Set[l.OperatorApplication] = { + def _identifyCountOps(es : Seq[l.Expr]) : CountingOpSet = { es.foldLeft(Set.empty[l.OperatorApplication]) { (acc, e) => acc ++ identifyCountOps(e) } @@ -59,7 +65,7 @@ class UMCRewriter(module : l.Module) { * * Note recursion is to identifyCountsOp which is a memo. */ - def _identifyCountOps(e : l.Expr) : Set[l.OperatorApplication] = { + def _identifyCountOps(e : l.Expr) : CountingOpSet = { e match { case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => Set.empty @@ -68,7 +74,7 @@ class UMCRewriter(module : l.Module) { case l.Tuple(es) => _identifyCountOps(es) case opapp : l.OperatorApplication => - val init : Set[l.OperatorApplication] = opapp.op match { + val init : CountingOpSet = opapp.op match { case l.CountingOp(_, _) => Set(opapp) case _ => Set.empty } @@ -83,10 +89,10 @@ class UMCRewriter(module : l.Module) { /** * Memoizing wrapper for finding all counting operators. */ - val identifyCountOps = new Memo[l.Expr, Set[l.OperatorApplication]](_identifyCountOps _) + val identifyCountOps = new Memo[l.Expr, CountingOpSet](_identifyCountOps _) /** Finding all the counting operators in a list of assert statements. */ - def identifyCountOps(proofBlk: List[l.AssertStmt]) : Set[l.OperatorApplication] = { + def identifyCountOps(proofBlk: List[l.AssertStmt]) : CountingOpSet = { proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { (acc, st) => acc ++ identifyCountOps(st.e) } @@ -118,7 +124,7 @@ class UMCRewriter(module : l.Module) { /** Generate UF decls for the identified counting operators. * */ - def generateUFDecls(ops : Set[l.OperatorApplication]) : (Map[l.OperatorApplication, l.FunctionDecl]) = { + def generateCountingOpToUFMap(ops : CountingOpSet) : (UFMap) = { ops.map { opapp => { assert (opapp.op.isInstanceOf[l.CountingOp]) @@ -131,11 +137,39 @@ class UMCRewriter(module : l.Module) { }.toMap } + /** + * Create the list of UF + Axiom declarations. + */ + def generateUFDecls(ufMap : UFMap) : List[l.Decl] = { + def geqZero(e : l.Expr) : l.Expr = { + l.OperatorApplication(l.IntGEOp(), List(e, l.IntLit(0))) + } + def quantify(ufDecl : l.FunctionDecl, e : l.Expr) : l.Expr = { + if (ufDecl.sig.args.size > 0) { + l.OperatorApplication(l.ForallOp(ufDecl.sig.args, List.empty), List(e)) + } else { + e + } + } + ufMap.map { + p => { + val ufDecl = p._2 + val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) + val axExpr = quantify(ufDecl, innerExpr) + val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) + List(ufDecl, axiomDecl) + } + }.flatten.toList + } + def process() : l.Module = { val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) val countingOps = identifyCountOps(proofProcBody) - val ufDecls = generateUFDecls(countingOps) + val ufMap = generateCountingOpToUFMap(countingOps) + val ufDecls = generateUFDecls(ufMap) + val moduleP = l.Module(module.id, module.decls ++ ufDecls, module.cmds, module.notes) + println(ufMap.toString()) println(ufDecls.toString()) - module + moduleP } } \ No newline at end of file diff --git a/src/main/scala/uclid/lang/ASTVistors.scala b/src/main/scala/uclid/lang/ASTVistors.scala index e53236903..96916db8b 100644 --- a/src/main/scala/uclid/lang/ASTVistors.scala +++ b/src/main/scala/uclid/lang/ASTVistors.scala @@ -743,6 +743,7 @@ class ASTAnalyzer[T] (_passName : String, _pass: ReadOnlyPass[T]) extends ASTAna case None => result case Some(id) => visitIdentifier(id, result, context) } + result = st.params.foldLeft(result)((acc, e) => visitExpr(e, acc, context)) val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment result = visitExpr(st.e, result, context.withEnvironment(envP)) result = pass.applyOnAssert(TraversalDirection.Up, st, result, context) @@ -1609,9 +1610,10 @@ class ASTRewriter (_passName : String, _pass: RewritePass, setFilename : Boolean def visitAssertStatement(st : AssertStmt, context : Scope) : Option[Statement] = { val idP = st.id.flatMap(id => visitIdentifier(id, context)) + val paramsP = st.params.map(p => visitExpr(p, context)).flatten val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment val stP = visitExpr(st.e, context.withEnvironment(envP)).flatMap((e) => { - pass.rewriteAssert(AssertStmt(e, idP), context) + pass.rewriteAssert(AssertStmt(e, idP, paramsP), context) }) return ASTNode.introducePos(setPosition, setFilename, stP, st.position) } diff --git a/src/main/scala/uclid/lang/ModularProductProgram.scala b/src/main/scala/uclid/lang/ModularProductProgram.scala index bf0bb6f07..ad149d3cb 100644 --- a/src/main/scala/uclid/lang/ModularProductProgram.scala +++ b/src/main/scala/uclid/lang/ModularProductProgram.scala @@ -524,7 +524,7 @@ class ModularProductProgramPass extends RewritePass { } } - case AssertStmt(expr, id) => + case AssertStmt(expr, id, params) => val activationVariableArray = helperObj.mapOfActivationVariables(currentScope) val emptyVarsList: List[BlockVarsDecl] = List() expr match { @@ -546,7 +546,8 @@ class ModularProductProgramPass extends RewritePass { andCondition = Operator.and(andCondition, checkActVarCondition) } val renamedExpression = getRenamedExpr(expr, context, k) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, k)) + val newAssertStatement = AssertStmt(renamedExpression, id, renamedParams) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -560,7 +561,8 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, i)) + val newAssertStatement = AssertStmt(renamedExpression, id, params) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -576,7 +578,8 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val newAssertStatement = AssertStmt(renamedExpression, id) + val renamedParams = params.map(p => getRenamedExpr(p, context, i)) + val newAssertStatement = AssertStmt(renamedExpression, id, params) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -1094,7 +1097,7 @@ class ModularProductProgramPass extends RewritePass { newstmts += oldstmts.head } - case AssertStmt(expr, _) => + case AssertStmt(expr, id, params) => expr match { case OperatorApplication(op, operands) => val hasHyperSelect = isHyperSelectPresent(op, operands) diff --git a/src/main/scala/uclid/lang/ModuleFlattener.scala b/src/main/scala/uclid/lang/ModuleFlattener.scala index de1c4749d..4e920957e 100644 --- a/src/main/scala/uclid/lang/ModuleFlattener.scala +++ b/src/main/scala/uclid/lang/ModuleFlattener.scala @@ -550,7 +550,7 @@ class ModuleInstantiatorPass(module : Module, inst : InstanceDecl, targetModule val preconditionAsserts : List[Statement] = proc.requires.map { (req) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(req, context), context) - val node = AssertStmt(exprP, Some(Identifier("precondition"))) + val node = AssertStmt(exprP, Some(Identifier("precondition")), List.empty) ASTNode.introducePos(true, true, node, req.position) } } @@ -559,7 +559,7 @@ class ModuleInstantiatorPass(module : Module, inst : InstanceDecl, targetModule proc.ensures.map { (ens) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(ens, context), context) - val node = AssertStmt(exprP, Some(Identifier("postcondition"))) + val node = AssertStmt(exprP, Some(Identifier("postcondition")), List.empty) ASTNode.introducePos(true, true, node, ens.position) } } diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 8701b9646..3a7445ffa 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -460,6 +460,7 @@ case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)] override val hashId = 1402 override val md5hashCode = computeMD5Hash(xs, ys) } + // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { override def fixity = Operator.INFIX @@ -1148,7 +1149,7 @@ case class HavocableInstanceId(opapp : OperatorApplication) extends HavocableEnt } /** Statements **/ -sealed abstract class Statement extends ASTNode { +abstract class Statement extends ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def hasStmtBlock = false val isLoop = false @@ -1164,8 +1165,17 @@ case class SkipStmt() extends Statement { override val hashId = 3000 override val md5hashCode = computeMD5Hash } -case class AssertStmt(e: Expr, id : Option[Identifier]) extends Statement { - override def toLines = List("assert " + e + "; // " + position.toString) +case class AssertStmt(e: Expr, id : Option[Identifier], params: List[Expr]) extends Statement { + override def toLines = { + val paramStr = if (params.size > 0) { + " [" + Utils.join(params.map(_.toString()), ", ") + "] " + } else { "" } + val prefix = id match { + case Some(n) => "assert " + n.toString() + paramStr + ": " + case None => "assert " + paramStr + } + List(prefix + e.toString() + "; // " + position.toString) + } override val hasCall = false override val hasInternalCall = false override val hashId = 3001 diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index e4e63fd52..fc0f06f82 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -5,8 +5,8 @@ module main { define h(n : integer) : boolean = n >= 5 && n <= 20; proof { - assert or: #[(n:integer) for () :: f(n)] == - #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; + assert disjoint: #[(n:integer) for () :: f(n)] == + #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; } } From 5ade17d084f714c1a95b10e6b0effea0aa050b1d Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 00:33:31 +0530 Subject: [PATCH 075/119] More fixes for assertion parameters. --- src/main/scala/uclid/lang/ModuleTypeChecker.scala | 2 +- .../scala/uclid/lang/PrimedAssignmentChecker.scala | 2 +- src/main/scala/uclid/lang/ProcedureInliner.scala | 4 ++-- src/main/scala/uclid/lang/StatementScheduler.scala | 12 ++++++------ src/main/scala/uclid/lang/UclidParser.scala | 2 +- src/main/scala/uclid/lang/WhileLoopRewriter.scala | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/scala/uclid/lang/ModuleTypeChecker.scala b/src/main/scala/uclid/lang/ModuleTypeChecker.scala index 141e65cff..5917d55fc 100644 --- a/src/main/scala/uclid/lang/ModuleTypeChecker.scala +++ b/src/main/scala/uclid/lang/ModuleTypeChecker.scala @@ -50,7 +50,7 @@ class ModuleTypeCheckerPass extends ReadOnlyPass[Set[ModuleError]] in } else { st match { - case AssertStmt(e, _) => + case AssertStmt(e, id, params) => val eType = exprTypeChecker.typeOf(e, context) if (!eType.isBool) { in + ModuleError("Assertion expression must be of Boolean or Temporal type", st.position) diff --git a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala index f8864503d..1244a5234 100644 --- a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala +++ b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala @@ -103,7 +103,7 @@ class PrimedAssignmentCheckerPass extends ReadOnlyPass[Set[ModuleError]] case IfElseStmt(_, _, _) | ForStmt(_, _, _, _) | WhileStmt(_, _, _) | CaseStmt(_) | SkipStmt() | - AssertStmt(_, _) | AssumeStmt(_, _) | + AssertStmt(_, _, _) | AssumeStmt(_, _) | HavocStmt(_) | BlockStmt(_, _) => in case ModuleCallStmt(_) => diff --git a/src/main/scala/uclid/lang/ProcedureInliner.scala b/src/main/scala/uclid/lang/ProcedureInliner.scala index b3ae57d3c..183ba2203 100644 --- a/src/main/scala/uclid/lang/ProcedureInliner.scala +++ b/src/main/scala/uclid/lang/ProcedureInliner.scala @@ -185,7 +185,7 @@ trait NewProcedureInlinerPass extends RewritePass { val preconditionAsserts : List[Statement] = proc.requires.map { (req) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(req, context), context) - val node = AssertStmt(exprP, Some(Identifier("precondition"))) + val node = AssertStmt(exprP, Some(Identifier("precondition")), List.empty) ASTNode.introducePos(true, true, node, req.position) } } @@ -194,7 +194,7 @@ trait NewProcedureInlinerPass extends RewritePass { proc.ensures.map { (ens) => { val exprP = oldRewriter.rewriteExpr(rewriter.rewriteExpr(ens, context), context) - val node = AssertStmt(exprP, Some(Identifier("postcondition"))) + val node = AssertStmt(exprP, Some(Identifier("postcondition")), List.empty) ASTNode.introducePos(true, true, node, ens.position) } } diff --git a/src/main/scala/uclid/lang/StatementScheduler.scala b/src/main/scala/uclid/lang/StatementScheduler.scala index 6386f4500..0e826828b 100644 --- a/src/main/scala/uclid/lang/StatementScheduler.scala +++ b/src/main/scala/uclid/lang/StatementScheduler.scala @@ -48,8 +48,8 @@ object StatementScheduler { def writeSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(_, _) => Set.empty - case AssumeStmt(_, _) => Set.empty + case AssertStmt(e, _, _) => Set.empty + case AssumeStmt(e, _) => Set.empty case HavocStmt(h) => h match { case HavocableId(id) => Set(id) @@ -111,8 +111,8 @@ object StatementScheduler { def writeSetIds(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(_, _) => Set.empty - case AssumeStmt(_, _) => Set.empty + case AssertStmt(e, _, _) => Set.empty + case AssumeStmt(e, _) => Set.empty case HavocStmt(h) => h match { case HavocableId(id) => Set(id) @@ -182,7 +182,7 @@ object StatementScheduler { def readSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(e, _) => readSet(e) + case AssertStmt(e, _, _) => readSet(e) case AssumeStmt(e, _) => readSet(e) case HavocStmt(_) => Set.empty case AssignStmt(lhss, rhss) => @@ -244,7 +244,7 @@ object StatementScheduler { def primeReadSet(st : Statement, context : Scope) : Set[Identifier] = { st match { case SkipStmt() => Set.empty - case AssertStmt(e, _) => primeReadSet(e) + case AssertStmt(e, _, _) => primeReadSet(e) case AssumeStmt(e, _) => primeReadSet(e) case HavocStmt(_) => Set.empty case AssignStmt(lhss, rhss) => diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 1b27d7e19..7cb7b1304 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -462,7 +462,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Statement: PackratParser[Statement] = positioned { KwSkip <~ ";" ^^ { case _ => SkipStmt() } | - KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None) } | + KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None, List.empty) } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ diff --git a/src/main/scala/uclid/lang/WhileLoopRewriter.scala b/src/main/scala/uclid/lang/WhileLoopRewriter.scala index e492e686b..6c81b5fc5 100644 --- a/src/main/scala/uclid/lang/WhileLoopRewriter.scala +++ b/src/main/scala/uclid/lang/WhileLoopRewriter.scala @@ -47,7 +47,7 @@ class WhileLoopRewriterPass extends RewritePass { val invs = whileSt.invariants val initialAsserts = invs.map{ inv => { - ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (entry)"))), inv.position) + ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (entry)")), List.empty), inv.position) } } val varsToHavoc = StatementScheduler.writeSetIds(whileSt.body, context).toList @@ -59,7 +59,7 @@ class WhileLoopRewriterPass extends RewritePass { val assumeStmts = AssumeStmt(cond, None) :: invs.map(inv => AssumeStmt(inv, None)) val assertStmts = invs.map{ inv => { - ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (iteration)"))), inv.position) + ASTNode.introducePos(true, true, AssertStmt(inv, Some(Identifier("loop invariant (iteration)")), List.empty), inv.position) } } val finishAssump = AssumeStmt(Operator.not(cond), None) From ee56eaf3c7b30f0b0d70eeff343df9f055ccdc72 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 11 Apr 2020 01:32:31 +0530 Subject: [PATCH 076/119] Almost done with the initial working example. --- .../extensions/modelcounts/UMCRewriter.scala | 94 ++++++++++++++++++- src/main/scala/uclid/lang/UclidLanguage.scala | 4 +- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 7a16cdb30..50009a9ef 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -120,7 +120,6 @@ class UMCRewriter(module : l.Module) { counters.put(prefix, cnt) name } - /** Generate UF decls for the identified counting operators. * */ @@ -161,13 +160,104 @@ class UMCRewriter(module : l.Module) { } }.flatten.toList } + + // Helper functions to more easily construct expressions. + def _forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + + def _and(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) + } + + def _or(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) + } + + def _iff(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IffOp(), List(e1, e2)) + } + + def _not(e : l.Expr) = { + l.OperatorApplication(l.NegationOp(), List(e)) + } + + def _eq(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.EqualityOp(), List(e1, e2)) + } + + def _plus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.AddOp(), List(e1, e2)) + } + + def extractCountingArgs(e : l.Expr) = { + assert (e.isInstanceOf[l.OperatorApplication]) + val opapp = e.asInstanceOf[l.OperatorApplication] + opapp.op match { + case l.CountingOp(l1, l2) => l1 ++ l2 + case _ => throw new AssertionError("Unexpected operator") + } + } + + def extractFunction(e : l.Expr) = { + assert (e.isInstanceOf[l.OperatorApplication]) + val opapp = e.asInstanceOf[l.OperatorApplication] + opapp.operands(0) + } + + def _apply(uf : l.FunctionDecl) = { + l.FuncApplication(uf.id, uf.sig.args.map(_._1)) + } + def rewriteDisjoint(ufMap : UFMap, st : l.AssertStmt) : List[l.Statement] = { + val e = st.e + e match { + case l.OperatorApplication(l.EqualityOp(), List(e1, l.OperatorApplication(l.AddOp(), List(e2, e3)))) => + val o1 = e1.asInstanceOf[l.OperatorApplication] + val o2 = e2.asInstanceOf[l.OperatorApplication] + val o3 = e3.asInstanceOf[l.OperatorApplication] + val args = extractCountingArgs(e1) + val f1 = extractFunction(e1) + val f2 = extractFunction(e2) + val f3 = extractFunction(e3) + val assertExpr = _and(_forall(args, _iff(f1, _or(f2, f3))), + _forall(args, _not(_and(f2, f3)))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(o1)) + val ufn2 = _apply(ufMap(o2)) + val ufn3 = _apply(ufMap(o3)) + val assumeExpr = _forall(args, _eq(ufn1, _plus(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + case _ => + throw new AssertionError("Unexpected expression in rewriteDisjoint: " + e.toString()) + } + } + def rewriteAssert(ufmap : UFMap, st : l.AssertStmt) : List[l.Statement] = { + st.id match { + case Some(l.Identifier("disjoint")) => + rewriteDisjoint(ufmap, st) + case _ => + throw new AssertionError("Unknown rule: " + st.id.toString()) + } + } + def process() : l.Module = { + val proofProc = module.procedures(0) val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) val countingOps = identifyCountOps(proofProcBody) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val moduleP = l.Module(module.id, module.decls ++ ufDecls, module.cmds, module.notes) + val newProofStmts = proofProcBody.map(st => rewriteAssert(ufMap, st)).flatten + val newProofProc = l.ProcedureDecl( + l.Identifier("newCountingProof"), proofProc.sig, + l.BlockStmt(List.empty, newProofStmts), + List.empty, List.empty, Set.empty, proofProc.annotations) + val prevDecls = module.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val moduleP = l.Module(module.id, + prevDecls ++ ufDecls ++ List(newProofProc), + module.cmds, module.notes) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 3a7445ffa..5cae24136 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -1703,10 +1703,10 @@ case class AxiomDecl(id : Option[Identifier], expr: Expr, params: List[ExprDecor override val hashId = 3918 override val md5hashCode = computeMD5Hash(id, expr, params) override def toString = { - id match { + (id match { case Some(id) => "axiom " + id.toString + " : " + expr.toString() case None => "axiom " + expr.toString - } + }) + "; // " + pos.toString() } override def declNames = id match { case Some(i) => List(i) From 57afb530cf074699c9ea2d64b5385d2c9e4674aa Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sun, 12 Apr 2020 02:05:38 +0530 Subject: [PATCH 077/119] Refactoring the UMC ASTs. This should make rewriting much cleaner. --- src/main/scala/uclid/UclidMain.scala | 13 +- .../extensions/modelcounts/UMCLanguage.scala | 117 +++++++++++ .../extensions/modelcounts/UMCMain.scala | 15 ++ .../extensions/modelcounts/UMCParser.scala | 68 +++---- .../extensions/modelcounts/UMCRewriter.scala | 191 ++++++------------ src/main/scala/uclid/lang/UclidLanguage.scala | 19 +- src/main/scala/uclid/lang/UclidParser.scala | 3 - test/modelcounter/hello.ucl | 8 +- 8 files changed, 235 insertions(+), 199 deletions(-) create mode 100644 src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala diff --git a/src/main/scala/uclid/UclidMain.scala b/src/main/scala/uclid/UclidMain.scala index 65fd275fc..cb249bbca 100644 --- a/src/main/scala/uclid/UclidMain.scala +++ b/src/main/scala/uclid/UclidMain.scala @@ -248,7 +248,6 @@ object UclidMain { def compile(srcFiles : Seq[java.io.File], mainModuleName : Identifier, test : Boolean = false): List[Module] = { type NameCountMap = Map[Identifier, Int] var nameCnt : NameCountMap = Map().withDefaultValue(0) - val passManager = createCompilePassManager(test, mainModuleName) val filenameAdderPass = new AddFilenameRewriter(None) // Helper function to parse a single file. @@ -261,7 +260,17 @@ object UclidMain { val parsedModules = srcFiles.foldLeft(List.empty[Module]) { (acc, srcFile) => acc ++ parseFile(srcFile.getPath()) } - + compileModules(parsedModules, mainModuleName, test) + } + + /** Compile a list of modules (do everything pre-module-instantiation. */ + def compileModules( + parsedModules : List[Module], + mainModuleName : Identifier, + test : Boolean) : List[Module] = + { + // create a pass manager. + val passManager = createCompilePassManager(test, mainModuleName) // now process each module val init = (List.empty[Module], Scope.empty) // NOTE: The foldLeft/:: combination here reverses the order of modules. diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala new file mode 100644 index 000000000..32a5cac95 --- /dev/null +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -0,0 +1,117 @@ +/* + * UCLID5 Verification and Synthesis Engine + * + * Copyright (c) 2017. + * Sanjit A. Seshia, Rohit Sinha and Pramod Subramanyan. + * + * All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Author: Pramod Subramanyan + * + * Main file for the UCLID model counter. + * + */ +package uclid.extensions.modelcounts + +import uclid.UclidMain +import uclid.{lang => l} +import uclid.Utils + + +/** CountingOp is a new operator we introduce for the UMC extension. */ +case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, l.Type)], e : l.Expr) extends l.Expr { + override def toString() = { + val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + val s2 = if (ys.size > 0) { + val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") + "#[(" + s1 + ") for (" + s2 + ")] :: " + } else { + "#[(" + s1 + ")] :: " + } + s2 + "(" + e.toString() + ")" + } + override val hashId = 1402 + override val md5hashCode = computeMD5Hash(xs, ys) +} + +/** This is the base class for all the "statements" in the proof. */ +abstract class Statement extends l.ASTNode { + override def toString = Utils.join(toLines, "\n") + "\n" + def toLines : List[String] + def expressions : Seq[CountingOp] +} + +case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + override val hashId = 130001 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def expressions = Seq(e1, e2, e3) +} + +case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { + override def toString = { + "module " + id.toString() + " {\n" + + Utils.join(decls.map(" " + _.toString()), "\n") + + "\n\n proof {\n" + + Utils.join(stmts.map(st => " " + st.toString()), "\n") + + "\n }\n}" + } + override val hashId = 131001 + override val md5hashCode = computeMD5Hash(id, decls, stmts) +} +/** Helpers to construct UCLID5 expressions. */ +object UMCExpressions { + // Helper functions to more easily construct expressions. + def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + + def and(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) + } + + def or(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) + } + + def iff(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IffOp(), List(e1, e2)) + } + + def not(e : l.Expr) = { + l.OperatorApplication(l.NegationOp(), List(e)) + } + + def eq(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.EqualityOp(), List(e1, e2)) + } + + def plus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.AddOp(), List(e1, e2)) + } +} diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 5f29be439..87661f2a6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -39,14 +39,29 @@ package uclid.extensions.modelcounts import uclid.UclidMain import uclid.{lang => l} +import uclid.Utils object UMCMain { + /** Executes regular UCLID5 on the processed module. */ + def runProcessedModel(module : l.Module) : Unit = { + val config = UclidMain.Config() + val mainModuleName = l.Identifier("main") + val modules = UclidMain.compileModules(List(module), mainModuleName, false) + val mainModule = UclidMain.instantiate(config, modules, mainModuleName, true) + mainModule match { + case Some(m) => UclidMain.execute(m, config) + case None => + throw new Utils.ParserError("Unable to find main module", None, None) + } + } + def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) println(module.toString()) val moduleP = new UMCRewriter(module).process() println(moduleP.toString()) + runProcessedModel(moduleP) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index fe8f879af..2e7f2888b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -40,63 +40,52 @@ package uclid.extensions.modelcounts import uclid.{lang => l} +import uclid.Utils import uclid.lang.Identifier import uclid.smt.IntLit - +import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" - lexical.reserved += (KwProof) - - lazy val ControlBlock : List[l.GenericProofCommand] = List( - l.GenericProofCommand( - l.Identifier("unroll"), - List.empty, List((l.IntLit(1), "1")), - Some(l.Identifier("v")), None), - l.GenericProofCommand( - l.Identifier("check"), - List.empty, List.empty, - None, None), - l.GenericProofCommand( - l.Identifier("print_results"), - List.empty, List.empty, - None, None), - l.GenericProofCommand( - l.Identifier("print_cex"), - List.empty, List.empty, - None, Some(l.Identifier("v"))) - ) + lazy val KwDisjoint = "disjoint" + + lexical.reserved += (KwProof, KwDisjoint) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) - lazy val AssertStmt: PackratParser[l.AssertStmt] = positioned { - KwAssert ~> Id ~ (":" ~> Expr <~ ";") ^^ { - case id ~ e => l.AssertStmt(e, Some(id), List.empty) + lazy val CountingExpr : PackratParser[CountingOp] = positioned { + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { + case xs ~ ys ~ e => CountingOp(xs, ys, e) + } | + ("#[" ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { + case xs ~ e => CountingOp(xs, List.empty, e) + } + } + + lazy val AssertStmt: PackratParser[Statement] = positioned { + KwAssert ~ KwDisjoint ~ ":" ~> + (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ + { + case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) } } - lazy val ProofStmt: PackratParser[l.AssertStmt] = + lazy val ProofStmt: PackratParser[Statement] = positioned ( AssertStmt ); - lazy val ProofScript: PackratParser[List[l.AssertStmt]] = { + lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } - lazy val UMCModule: PackratParser[l.Module] = positioned { + lazy val CntProof: PackratParser[CountingProof] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - val proc = l.ProcedureDecl( - l.Identifier("countingProof"), // procedure name - l.ProcedureSig(List.empty, List.empty), // signature - l.BlockStmt(List.empty, proof), // body - List.empty, List.empty, Set.empty, // requires, ensures, modifies - l.ProcedureAnnotations(Set.empty)) // no annotations. - l.Module(id, decls ++ List(proc) , ControlBlock, List.empty) + CountingProof(id, decls, proof) } } } - def parseUMCModel(filename : String, text: String): l.Module = { + def parseUMCModel(filename : String, text: String): CountingProof = { val tokens = new PackratReader(new lexical.Scanner(text)) - phrase(UMCModule)(tokens) match { + phrase(CntProof)(tokens) match { case Success(module, _) => module case NoSuccess(msg, next) => throw new uclid.Utils.SyntaxError(msg, Some(next.pos), Some(filename)) } @@ -106,12 +95,9 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() val filenameAdderPass = new l.AddFilenameRewriter(None) - def parseUMCModel(file: java.io.File) : l.Module = { + def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString - filenameAdderPass.setFilename(filePath) - filenameAdderPass.visit( - parserObj.parseUMCModel(filePath, text), - l.Scope.empty).get + parserObj.parseUMCModel(filePath, text) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 50009a9ef..8f1ef82c3 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -40,66 +40,25 @@ package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.Memo - +import uclid.extensions.modelcounts.{UMCExpressions => E} import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} -class UMCRewriter(module : l.Module) { - val proofProcedure = module.procedures(0) - +class UMCRewriter(cntProof : CountingProof) { /* We will be using this set in a number of places. */ - type CountingOpSet = Set[l.OperatorApplication] + type CountingOpSet = Set[CountingOp] /* A map from counting ops to the UFs that represent them. */ - type UFMap = Map[l.OperatorApplication, l.FunctionDecl] + type UFMap = Map[CountingOp, l.FunctionDecl] - /** Identify counting ops in a sequence of expressions. - * - * Note the recursion is to identifyCountOps which is a Memo. - */ - def _identifyCountOps(es : Seq[l.Expr]) : CountingOpSet = { - es.foldLeft(Set.empty[l.OperatorApplication]) { - (acc, e) => acc ++ identifyCountOps(e) - } - } - /** Identify counting ops in an expression. - * - * Note recursion is to identifyCountsOp which is a memo. - */ - def _identifyCountOps(e : l.Expr) : CountingOpSet = { - e match { - case _ : l.Identifier | _ : l.ExternalIdentifier | _ : l.Literal => - Set.empty - case l.ConstArray(e, typ) => - identifyCountOps(e) - case l.Tuple(es) => - _identifyCountOps(es) - case opapp : l.OperatorApplication => - val init : CountingOpSet = opapp.op match { - case l.CountingOp(_, _) => Set(opapp) - case _ => Set.empty - } - init ++ _identifyCountOps(opapp.operands) - case l.FuncApplication(e, args) => - identifyCountOps(e) ++ _identifyCountOps(args) - case l.Lambda(ids, e) => - identifyCountOps(e) - } - } - - /** - * Memoizing wrapper for finding all counting operators. - */ - val identifyCountOps = new Memo[l.Expr, CountingOpSet](_identifyCountOps _) - /** Finding all the counting operators in a list of assert statements. */ - def identifyCountOps(proofBlk: List[l.AssertStmt]) : CountingOpSet = { - proofBlk.foldLeft(Set.empty[l.OperatorApplication]) { - (acc, st) => acc ++ identifyCountOps(st.e) + def identifyCountOps(proofBlk: List[Statement]) : CountingOpSet = { + proofBlk.foldLeft(Set.empty[CountingOp]) { + (acc, st) => acc ++ st.expressions.toSet } } /** Identifiers that are already declared in the module. */ - val existingIds = module.decls.map(d => d.declNames).flatten.toSet + val existingIds = cntProof.decls.map(d => d.declNames).flatten.toSet /** Identifiers that are declared + newly generated names. */ val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds /** Counters that track (roughly) the number of generated identifiers with each prefix. */ @@ -125,13 +84,11 @@ class UMCRewriter(module : l.Module) { */ def generateCountingOpToUFMap(ops : CountingOpSet) : (UFMap) = { ops.map { - opapp => { - assert (opapp.op.isInstanceOf[l.CountingOp]) - val op = opapp.op.asInstanceOf[l.CountingOp] + op => { val ufId = generateId("count") val sig = l.FunctionSig(op.ys, l.IntegerType()) val uf = l.FunctionDecl(ufId, sig) - opapp -> uf + op -> uf } }.toMap } @@ -161,103 +118,71 @@ class UMCRewriter(module : l.Module) { }.flatten.toList } - // Helper functions to more easily construct expressions. - def _forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - val op = l.ForallOp(vs, List.empty) - l.OperatorApplication(op, List(e)) - } - - def _and(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) - } - - def _or(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) - } - - def _iff(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.IffOp(), List(e1, e2)) - } - - def _not(e : l.Expr) = { - l.OperatorApplication(l.NegationOp(), List(e)) - } - - def _eq(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.EqualityOp(), List(e1, e2)) - } - - def _plus(e1 : l.Expr, e2 : l.Expr) = { - l.OperatorApplication(l.AddOp(), List(e1, e2)) - } - - def extractCountingArgs(e : l.Expr) = { - assert (e.isInstanceOf[l.OperatorApplication]) - val opapp = e.asInstanceOf[l.OperatorApplication] - opapp.op match { - case l.CountingOp(l1, l2) => l1 ++ l2 - case _ => throw new AssertionError("Unexpected operator") - } - } - - def extractFunction(e : l.Expr) = { - assert (e.isInstanceOf[l.OperatorApplication]) - val opapp = e.asInstanceOf[l.OperatorApplication] - opapp.operands(0) + def extractCountingArgs(e : CountingOp) = { + e.xs ++ e.ys } def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } - def rewriteDisjoint(ufMap : UFMap, st : l.AssertStmt) : List[l.Statement] = { - val e = st.e - e match { - case l.OperatorApplication(l.EqualityOp(), List(e1, l.OperatorApplication(l.AddOp(), List(e2, e3)))) => - val o1 = e1.asInstanceOf[l.OperatorApplication] - val o2 = e2.asInstanceOf[l.OperatorApplication] - val o3 = e3.asInstanceOf[l.OperatorApplication] - val args = extractCountingArgs(e1) - val f1 = extractFunction(e1) - val f2 = extractFunction(e2) - val f3 = extractFunction(e3) - val assertExpr = _and(_forall(args, _iff(f1, _or(f2, f3))), - _forall(args, _not(_and(f2, f3)))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) - val ufn1 = _apply(ufMap(o1)) - val ufn2 = _apply(ufMap(o2)) - val ufn3 = _apply(ufMap(o3)) - val assumeExpr = _forall(args, _eq(ufn1, _plus(ufn2, ufn3))) - val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) - case _ => - throw new AssertionError("Unexpected expression in rewriteDisjoint: " + e.toString()) - } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + val o1 = st.e1 + val o2 = st.e2 + val o3 = st.e3 + val args = extractCountingArgs(o1) + val f1 = o1.e + val f2 = o2.e + val f3 = o3.e + val assertExpr = E.and(E.forall(args, E.iff(f1, E.or(f2, f3))), + E.forall(args, E.not(E.and(f2, f3)))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(o1)) + val ufn2 = _apply(ufMap(o2)) + val ufn3 = _apply(ufMap(o3)) + val assumeExpr = E.forall(args, E.eq(ufn1, E.plus(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) } - def rewriteAssert(ufmap : UFMap, st : l.AssertStmt) : List[l.Statement] = { - st.id match { - case Some(l.Identifier("disjoint")) => - rewriteDisjoint(ufmap, st) + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { + st match { + case d : DisjointStmt => + rewriteDisjoint(ufmap, d) case _ => - throw new AssertionError("Unknown rule: " + st.id.toString()) + throw new AssertionError("Unknown proof statement: " + st.toString()) } } + lazy val controlBlock : List[l.GenericProofCommand] = List( + l.GenericProofCommand( + l.Identifier("verify"), + List.empty, List((l.Identifier("countingProof"), "countingProof")), + Some(l.Identifier("v")), None), + l.GenericProofCommand( + l.Identifier("check"), + List.empty, List.empty, + None, None), + l.GenericProofCommand( + l.Identifier("print_results"), + List.empty, List.empty, + None, None), + ) + + def process() : l.Module = { - val proofProc = module.procedures(0) - val proofProcBody = module.procedures(0).body.asInstanceOf[l.BlockStmt].stmts.map(_.asInstanceOf[l.AssertStmt]) - val countingOps = identifyCountOps(proofProcBody) + val countingOps = identifyCountOps(cntProof.stmts) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val newProofStmts = proofProcBody.map(st => rewriteAssert(ufMap, st)).flatten + val newProofStmts = cntProof.stmts.map(st => rewriteAssert(ufMap, st)).flatten val newProofProc = l.ProcedureDecl( - l.Identifier("newCountingProof"), proofProc.sig, + l.Identifier("countingProof"), + l.ProcedureSig(List.empty, List.empty), l.BlockStmt(List.empty, newProofStmts), - List.empty, List.empty, Set.empty, proofProc.annotations) - val prevDecls = module.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) - val moduleP = l.Module(module.id, + List.empty, List.empty, Set.empty, l.ProcedureAnnotations(Set.empty)) + val prevDecls = cntProof.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val moduleP = l.Module(cntProof.id, prevDecls ++ ufDecls ++ List(newProofProc), - module.cmds, module.notes) + controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 5cae24136..e37561569 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -159,7 +159,7 @@ object Operator { def oldInstance(c : OperatorApplication) = OperatorApplication(OldOperator(), List(c)) def history(c : Identifier, e : Expr) = OperatorApplication(HistoryOperator(), List(c, e)) } -trait Operator extends ASTNode { +sealed trait Operator extends ASTNode { def fixity : Int def isPolymorphic = false def isTemporal = false @@ -449,17 +449,6 @@ case class ExistsOp(vs: List[(Identifier, Type)], patterns: List[List[Expr]]) ex override val hashId = 1401 override val md5hashCode = computeMD5Hash(vs, patterns) } -/** CountingOp is used in the model counting extension. */ -case class CountingOp(xs: List[(Identifier, Type)], ys: List[(Identifier, Type)]) extends Operator { - override def toString() = { - val s1 = Utils.join(xs.map(v => v._1.toString() + " : " + v._2.toString()), ", ") - val s2 = Utils.join(ys.map(v => v._1.toString() + " : " + v._2.toString()), ", ") - "#[(" + s1 + ") for (" + s2 + ") :: " - } - override def fixity = Operator.PREFIX - override val hashId = 1402 - override val md5hashCode = computeMD5Hash(xs, ys) -} // (In-)equality operators. sealed abstract class ComparisonOperator() extends Operator { @@ -658,7 +647,7 @@ case class DistinctOp() extends Operator { override val hashId = 1709 override val md5hashCode = computeMD5Hash } -sealed abstract class Expr extends ASTNode { +abstract class Expr extends ASTNode { /** Is this value a statically-defined constant? */ def isConstant = false def isTemporal = false @@ -757,8 +746,6 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString - case CountingOp(_, _) => - op.toString() + operands(0).toString() + "]" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => @@ -1754,7 +1741,7 @@ case class GenericProofCommand( override def toString = { val nameStr = name.toString val paramStr = if (params.size > 0) { "[" + Utils.join(params.map(_.toString), ", ") + "]" } else { "" } - val argStr = if (args.size > 0) { "(" + Utils.join(args.map(_.toString), ", ") + ")" } else { "" } + val argStr = if (args.size > 0) { "(" + Utils.join(args.map(a => a._1.toString + " /* " + a._2 + "*/"), ", ") + ")" } else { "" } val resultStr = resultVar match { case Some(id) => id.toString + " = "; case None => "" } val objStr = argObj match { case Some(id) => id.toString + "->"; case None => "" } resultStr + objStr + nameStr + paramStr + argStr + ";" + " // " + position.toString diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 7cb7b1304..16e19f355 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -375,9 +375,6 @@ class UclidParser extends UclidTokenParsers with PackratParsers { KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => Lambda(idtyps, expr) } | "(" ~> Expr <~ ")" | Id <~ OpPrime ^^ { case id => lang.OperatorApplication(GetNextValueOp(), List(id)) } | - ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("::" ~> E1 <~ "]") ^^ { - case xs ~ ys ~ e => OperatorApplication(CountingOp(xs, ys), List(e)) - } | Id } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index fc0f06f82..0bba737f0 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,12 +1,12 @@ module main { - define f(n : integer) : boolean = n >= 5 && n <= 10; + define f(n : integer) : boolean = n >= 5 && n <= 20; define g(n : integer) : boolean = n >= 11 && n <= 20; - define h(n : integer) : boolean = n >= 5 && n <= 20; + define h(n : integer) : boolean = n >= 5 && n <= 10; proof { - assert disjoint: #[(n:integer) for () :: f(n)] == - #[(n:integer) for () :: g(n)] + #[(n:integer) for () :: h(n)]; + assert disjoint: #[(n:integer) for ()] :: f(n) == + #[(n:integer)] :: g(n) + #[(n:integer)] :: h(n); } } From 02a20ada5f74664860d4d5d3e550b84b9b02d06c Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 15 Apr 2020 02:38:12 +0530 Subject: [PATCH 078/119] We have added support for the range rule. --- .../extensions/modelcounts/UMCLanguage.scala | 112 +++++++++++++++++- .../extensions/modelcounts/UMCParser.scala | 37 ++++-- .../extensions/modelcounts/UMCRewriter.scala | 14 ++- src/main/scala/uclid/lang/Scope.scala | 31 +++++ test/modelcounter/hello.ucl | 12 +- 5 files changed, 188 insertions(+), 18 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 32a5cac95..06ad4a249 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -59,17 +59,92 @@ case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, } /** This is the base class for all the "statements" in the proof. */ -abstract class Statement extends l.ASTNode { +sealed abstract class Statement extends l.ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def toLines : List[String] - def expressions : Seq[CountingOp] + def countingOps : Seq[CountingOp] + def expressions: Seq[l.Expr] + def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] } case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def countingOps = Seq(e1, e2, e3) override def expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(DisjointStmt(op1, op2, op3)) + case _ => None + } + } +} + +case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { + lazy val lb : l.Expr = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + lb + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + lazy val ub : l.Expr = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + ub + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + lazy val v : l.Identifier = { + op.e match { + case l.OperatorApplication(l.ConjunctionOp(), + List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => + assert(u == v) + l.Identifier(v) + case _ => + throw new Utils.AssertionError("Unexpected operand to range expression.") + } + } + override val hashId = 130002 + override val md5hashCode = computeMD5Hash(op, cnt) + override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) + override def countingOps = Seq(op) + override def expressions = Seq(op, cnt) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(op.e), rewriter(cnt)) match { + case (Some(ep), Some(cntp)) => + val op1 = CountingOp(op.xs, op.ys, ep) + Some(RangeStmt(op1, cntp)) + case _ => None + } + } +} +case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130003 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) + override def countingOps = Seq(e) + override def expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } } case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { @@ -80,9 +155,18 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S Utils.join(stmts.map(st => " " + st.toString()), "\n") + "\n }\n}" } + def rewriteStatments(rewriter : l.ASTRewriter) : CountingProof = { + val ctx = decls.foldLeft(l.Scope.empty)((acc, d) => acc + d) + def rewriterFn(expr : l.Expr) : Option[l.Expr] = { + rewriter.visitExpr(expr, ctx) + } + val stmtsP = stmts.map(st => st.rewrite(rewriterFn)).flatten + CountingProof(id, decls, stmtsP) + } override val hashId = 131001 override val md5hashCode = computeMD5Hash(id, decls, stmts) } + /** Helpers to construct UCLID5 expressions. */ object UMCExpressions { // Helper functions to more easily construct expressions. @@ -114,4 +198,28 @@ object UMCExpressions { def plus(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.AddOp(), List(e1, e2)) } + + def minus(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.SubOp(), List(e1, e2)) + } + + def le(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntLEOp(), List(e1, e2)) + } + + def lt(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntLTOp(), List(e1, e2)) + } + + def rng(e1 : l.Expr, e2 : l.Expr, e3 : l.Expr) = { + and(le(e1, e2), lt(e2, e3)) + } + + def ite(c : l.Expr, e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ITEOp(), List(c, e1, e2)) + } + + def max(e1 : l.Expr, e2 : l.Expr) = { + ite(lt(e1, e2), e2, e1) + } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 2e7f2888b..eb501e3a9 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -42,32 +42,48 @@ package uclid.extensions.modelcounts import uclid.{lang => l} import uclid.Utils import uclid.lang.Identifier +import uclid.lang.Type import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lazy val KwDisjoint = "disjoint" + lazy val KwConstLB = "constLB" - lexical.reserved += (KwProof, KwDisjoint) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) + lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = + ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { + case xs ~ ys => (xs, ys) + } | + ("#[" ~> IdTypeList) <~ "]" ~ "::" ^^ { + case xs => (xs, List.empty) + } + lazy val CountingExpr : PackratParser[CountingOp] = positioned { - ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { - case xs ~ ys ~ e => CountingOp(xs, ys, e) - } | - ("#[" ~> IdTypeList) ~ ("]" ~ "::" ~> Expr) ^^ { - case xs ~ e => CountingOp(xs, List.empty, e) + CountingOpPrefix ~ ("(" ~> Expr <~ ")") ^^ { + case xs ~ e => CountingOp(xs._1, xs._2, e) } } lazy val AssertStmt: PackratParser[Statement] = positioned { KwAssert ~ KwDisjoint ~ ":" ~> - (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ - { + (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + } | + KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { + case e1 ~ e2 => { + RangeStmt(e1, e2) + } + } | + KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstLbStmt(e, v) + } } } lazy val ProofStmt: PackratParser[Statement] = @@ -94,10 +110,11 @@ class UMCParser extends l.UclidParser { object UMCParser { val parserObj = new UMCParser() - val filenameAdderPass = new l.AddFilenameRewriter(None) + val rewriter = new l.RewriteDefines() def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString - parserObj.parseUMCModel(filePath, text) + val model = parserObj.parseUMCModel(filePath, text) + model.rewriteStatments(rewriter) } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 8f1ef82c3..16ab729ea 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -53,7 +53,7 @@ class UMCRewriter(cntProof : CountingProof) { /** Finding all the counting operators in a list of assert statements. */ def identifyCountOps(proofBlk: List[Statement]) : CountingOpSet = { proofBlk.foldLeft(Set.empty[CountingOp]) { - (acc, st) => acc ++ st.expressions.toSet + (acc, st) => acc ++ st.countingOps.toSet } } @@ -144,10 +144,22 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.op) + val ufn = _apply(ufMap(st.op)) + val assumeExpr = E.forall(args, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + val assertExpr = E.forall(args, E.eq(ufn, st.cnt)) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + List(assumeStmt, assertStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { case d : DisjointStmt => rewriteDisjoint(ufmap, d) + case r : RangeStmt => + rewriteRange(ufmap, r) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/src/main/scala/uclid/lang/Scope.scala b/src/main/scala/uclid/lang/Scope.scala index 5fda03d3c..91b1b9a0e 100644 --- a/src/main/scala/uclid/lang/Scope.scala +++ b/src/main/scala/uclid/lang/Scope.scala @@ -244,6 +244,37 @@ case class Scope ( Scope(map + (m.id -> Scope.ModuleDefinition(m)), module, procedure, cmd, environment, parent) } + def +(d: Decl) : Scope = { + val mapP = d match { + case instD : InstanceDecl => + Scope.addToMap(map, Scope.Instance(instD)) + case ProcedureDecl(id, sig, _, _, _, _, _) => Scope.addToMap(map, Scope.Procedure(id, sig.typ)) + case TypeDecl(id, typ) => Scope.addToMap(map, Scope.TypeSynonym(id, typ)) + case StateVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.StateVar(id, typ))) + case InputVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.InputVar(id, typ))) + case OutputVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.OutputVar(id, typ))) + case SharedVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.SharedVar(id, typ))) + case ConstantLitDecl(id, lit) => Scope.addToMap(map, Scope.ConstantLit(id, lit)) + case ConstantsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.ConstantVar(id, typ))) + case GrammarDecl(id, sig, _) => Scope.addToMap(map, Scope.Grammar(id, sig.typ)) + case FunctionDecl(id, sig) => Scope.addToMap(map, Scope.Function(id, sig.typ)) + case SynthesisFunctionDecl(id, sig, _, _, _) => Scope.addToMap(map, Scope.Function(id, sig.typ)) // FIXME + case DefineDecl(id, sig, expr) => Scope.addToMap(map, Scope.Define(id, sig.typ, DefineDecl(id, sig, expr))) + case SpecDecl(id, expr, params) => Scope.addToMap(map, Scope.SpecVar(id, expr, params)) + case AxiomDecl(sId, expr, params) => sId match { + case Some(id) => Scope.addToMap(map, Scope.AxiomVar(id, expr, params)) + case None => map + } + //case ModuleConstantsImportDecl(id) => Scope.addToMap(mapAcc, Scope.ConstantsImport(id)) + //case ModuleFunctionsImportDecl(id) => Scope.addToMap(mapAcc, Scope.FunctionsImport(id)) + case ModuleConstantsImportDecl(_) => map + case ModuleFunctionsImportDecl(_) => map + case ModuleTypesImportDecl(_) | + ModuleDefinesImportDecl(_) | + InitDecl(_) | NextDecl(_) => map + } + Scope(mapP, None, None, None, environment, parent) + } /** Return a new context with the declarations in this module added to it. */ def +(m: Module) : Scope = { Utils.assert(module.isEmpty, "A module was already added to this Context.") diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 0bba737f0..0d45eb0b3 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -1,12 +1,14 @@ module main { - define f(n : integer) : boolean = n >= 5 && n <= 20; - define g(n : integer) : boolean = n >= 11 && n <= 20; - define h(n : integer) : boolean = n >= 5 && n <= 10; + define f(n : integer) : boolean = 5 <= n < 21; + define g(n : integer) : boolean = 11 <= n < 21; + define h(n : integer) : boolean = 5 <= n < 11; proof { - assert disjoint: #[(n:integer) for ()] :: f(n) == - #[(n:integer)] :: g(n) + #[(n:integer)] :: h(n); + assert range: #[(n:integer)] :: (f(n)) == 16; + assert constLB: #[(n:integer)] :: (g(n)) >= 5; + assert disjoint: #[(n:integer) for ()] :: (f(n)) == + #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } From be834da3f0a61498b5bfce6e30a18f899154ead7 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Thu, 16 Apr 2020 01:00:00 +0530 Subject: [PATCH 079/119] constlb and constub rules. --- .../extensions/modelcounts/UMCLanguage.scala | 38 ++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 8 +++- .../extensions/modelcounts/UMCRewriter.scala | 44 +++++++++++++++++++ src/main/scala/uclid/lang/UclidLanguage.scala | 2 +- test/modelcounter/hello.ucl | 2 + 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 06ad4a249..9264e2679 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -147,6 +147,22 @@ case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { } } +case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130004 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) + override def countingOps = Seq(e) + override def expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + @@ -175,6 +191,11 @@ object UMCExpressions { l.OperatorApplication(op, List(e)) } + def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + val op = l.ExistsOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } + def and(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) } @@ -210,6 +231,15 @@ object UMCExpressions { def lt(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IntLTOp(), List(e1, e2)) } + + def ge(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntGEOp(), List(e1, e2)) + } + + def gt(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.IntGTOp(), List(e1, e2)) + } + def rng(e1 : l.Expr, e2 : l.Expr, e3 : l.Expr) = { and(le(e1, e2), lt(e2, e3)) @@ -222,4 +252,12 @@ object UMCExpressions { def max(e1 : l.Expr, e2 : l.Expr) = { ite(lt(e1, e2), e2, e1) } + + def distinct(es : List[l.Expr]) = { + if (es.size == 1) { + es(0) + } else { + l.OperatorApplication(l.DistinctOp(), es) + } + } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index eb501e3a9..300386934 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -50,8 +50,9 @@ class UMCParser extends l.UclidParser { lazy val KwProof = "proof" lazy val KwDisjoint = "disjoint" lazy val KwConstLB = "constLB" + lazy val KwConstUB = "constUB" - lexical.reserved += (KwProof, KwDisjoint, KwConstLB) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) @@ -84,6 +85,11 @@ class UMCParser extends l.UclidParser { case e ~ v => { ConstLbStmt(e, v) } + } | + KwAssert ~ KwConstUB ~ ":" ~> CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstUbStmt(e, v) + } } } lazy val ProofStmt: PackratParser[Statement] = diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 16ab729ea..534560bab 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -154,12 +154,56 @@ class UMCRewriter(cntProof : CountingProof) { List(assumeStmt, assertStmt) } + def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.e) + val argVars = args.map(a => a._1) + val cnt = st.v.value.toInt + val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) + val existsVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] + val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) + val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val query = E.exists(existsVars, E.and(conjunction, distincts)) + val assertStmt = l.AssertStmt(query, None, List.empty) + val ufn = _apply(ufMap(st.e)) + val assumeExpr = E.forall(args, E.ge(ufn, st.v)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + + def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { + val args = extractCountingArgs(st.e) + val argVars = args.map(a => a._1) + val cnt = st.v.value.toInt + val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) + val vs : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] + val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) + val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val query = E.not(E.exists(vs, E.and(conjunction, distincts))) + val assertStmt = l.AssertStmt(query, None, List.empty) + val ufn = _apply(ufMap(st.e)) + val assumeExpr = E.forall(args, E.lt(ufn, st.v)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { case d : DisjointStmt => rewriteDisjoint(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) + case lb : ConstLbStmt => + rewriteConstLb(ufmap, lb) + case ub : ConstUbStmt => + rewriteConstUb(ufmap, ub) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index e37561569..1c495acb3 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -643,7 +643,7 @@ case class GetNextValueOp() extends Operator { } case class DistinctOp() extends Operator { override def toString = "distinct" - override def fixity = Operator.INFIX + override def fixity = Operator.PREFIX override val hashId = 1709 override val md5hashCode = computeMD5Hash } diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index 0d45eb0b3..a8dfe8849 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -7,6 +7,8 @@ module main { proof { assert range: #[(n:integer)] :: (f(n)) == 16; assert constLB: #[(n:integer)] :: (g(n)) >= 5; + assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; + assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; assert disjoint: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } From 8120ee51b99fab00da6cf8a525de65e10a5ac292 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Fri, 17 Apr 2020 19:27:56 +0530 Subject: [PATCH 080/119] cover properties properly support. --- src/main/scala/uclid/AssertionTree.scala | 16 +++++++++--- src/main/scala/uclid/SymbolicSimulator.scala | 6 ++--- .../extensions/modelcounts/UMCLanguage.scala | 4 +-- .../extensions/modelcounts/UMCRewriter.scala | 2 +- src/main/scala/uclid/lang/ASTVistors.scala | 5 ++-- .../uclid/lang/ModularProductProgram.scala | 13 ++++------ .../scala/uclid/lang/ModuleTypeChecker.scala | 2 +- .../uclid/lang/PrimedAssignmentChecker.scala | 10 ++++---- src/main/scala/uclid/lang/UclidLanguage.scala | 25 +++++++++++-------- src/main/scala/uclid/lang/UclidParser.scala | 4 +++ test/debug/mc.ucl | 17 +++++++++++++ test/modelcounter/counting_2n.ucl | 19 ++++++++++++++ 12 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 test/debug/mc.ucl create mode 100644 test/modelcounter/counting_2n.ucl diff --git a/src/main/scala/uclid/AssertionTree.scala b/src/main/scala/uclid/AssertionTree.scala index bd78d22c3..ec6a6a249 100644 --- a/src/main/scala/uclid/AssertionTree.scala +++ b/src/main/scala/uclid/AssertionTree.scala @@ -175,10 +175,18 @@ class AssertionTree { solver.curAssertName = e.name solver.curAssertLabel = e.label val sat = solver.check(getModel) - val result = sat.result match { - case Some(true) => smt.SolverResult(Some(false), sat.model) - case Some(false) => smt.SolverResult(Some(true), sat.model) - case None => smt.SolverResult(None, None) + val result = if (e.decorators.contains(SATOnlyDecorator)) { + sat.result match { + case Some(true) => smt.SolverResult(Some(true), sat.model) + case Some(false) => smt.SolverResult(Some(false), sat.model) + case None => smt.SolverResult(None, None) + } + } else { + sat.result match { + case Some(true) => smt.SolverResult(Some(false), sat.model) + case Some(false) => smt.SolverResult(Some(true), sat.model) + case None => smt.SolverResult(None, None) + } } solver.pop() Some(CheckResult(e, result)) diff --git a/src/main/scala/uclid/SymbolicSimulator.scala b/src/main/scala/uclid/SymbolicSimulator.scala index 187163d2b..ab97ddd16 100644 --- a/src/main/scala/uclid/SymbolicSimulator.scala +++ b/src/main/scala/uclid/SymbolicSimulator.scala @@ -1741,7 +1741,7 @@ class SymbolicSimulator (module : Module) { frameLog.debug("symbolTable: %s".format(symbolTable.toString())) s match { case SkipStmt() => return symbolTable - case AssertStmt(e, id, params) => + case AssertStmt(e, id, decorators) => val frameTableP = frameTable.clone() frameTableP += symbolTable val simTable = ArrayBuffer(frameTableP) @@ -1753,7 +1753,7 @@ class SymbolicSimulator (module : Module) { val assert = AssertInfo( assertionName, label, simTable.clone(), scope, frameNumber, pathCondExpr, - assertExpr, List.empty, s.position) + assertExpr, decorators, s.position) assertLog.debug("Assertion: {}", e.toString) assertLog.debug("VC: {}", assertExpr.toString) frameLog.debug("FrameTableSize: {}", frameTableP.size) @@ -1829,7 +1829,7 @@ class SymbolicSimulator (module : Module) { def writeSet(stmt: Statement) : Set[Identifier] = stmt match { case SkipStmt() => Set.empty - case AssertStmt(e, id, params) => Set.empty + case AssertStmt(e, id, _) => Set.empty case AssumeStmt(e, id) => Set.empty case HavocStmt(h) => h match { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 9264e2679..304d0a872 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -254,8 +254,8 @@ object UMCExpressions { } def distinct(es : List[l.Expr]) = { - if (es.size == 1) { - es(0) + if (es.size <= 1) { + l.BoolLit(true) } else { l.OperatorApplication(l.DistinctOp(), es) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 534560bab..c5c62f063 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -167,7 +167,7 @@ class UMCRewriter(cntProof : CountingProof) { val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) val query = E.exists(existsVars, E.and(conjunction, distincts)) - val assertStmt = l.AssertStmt(query, None, List.empty) + val assertStmt = l.AssertStmt(query, None, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) val assumeExpr = E.forall(args, E.ge(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) diff --git a/src/main/scala/uclid/lang/ASTVistors.scala b/src/main/scala/uclid/lang/ASTVistors.scala index 96916db8b..8a9ed83cd 100644 --- a/src/main/scala/uclid/lang/ASTVistors.scala +++ b/src/main/scala/uclid/lang/ASTVistors.scala @@ -743,7 +743,6 @@ class ASTAnalyzer[T] (_passName : String, _pass: ReadOnlyPass[T]) extends ASTAna case None => result case Some(id) => visitIdentifier(id, result, context) } - result = st.params.foldLeft(result)((acc, e) => visitExpr(e, acc, context)) val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment result = visitExpr(st.e, result, context.withEnvironment(envP)) result = pass.applyOnAssert(TraversalDirection.Up, st, result, context) @@ -1610,10 +1609,10 @@ class ASTRewriter (_passName : String, _pass: RewritePass, setFilename : Boolean def visitAssertStatement(st : AssertStmt, context : Scope) : Option[Statement] = { val idP = st.id.flatMap(id => visitIdentifier(id, context)) - val paramsP = st.params.map(p => visitExpr(p, context)).flatten val envP = if (context.environment == ProceduralEnvironment) ProceduralAssertEnvironment else AssertEnvironment + // TODO: implement visitDecorator val stP = visitExpr(st.e, context.withEnvironment(envP)).flatMap((e) => { - pass.rewriteAssert(AssertStmt(e, idP, paramsP), context) + pass.rewriteAssert(AssertStmt(e, idP, st.decorators), context) }) return ASTNode.introducePos(setPosition, setFilename, stP, st.position) } diff --git a/src/main/scala/uclid/lang/ModularProductProgram.scala b/src/main/scala/uclid/lang/ModularProductProgram.scala index ad149d3cb..5da7956df 100644 --- a/src/main/scala/uclid/lang/ModularProductProgram.scala +++ b/src/main/scala/uclid/lang/ModularProductProgram.scala @@ -524,7 +524,7 @@ class ModularProductProgramPass extends RewritePass { } } - case AssertStmt(expr, id, params) => + case AssertStmt(expr, id, decorators) => val activationVariableArray = helperObj.mapOfActivationVariables(currentScope) val emptyVarsList: List[BlockVarsDecl] = List() expr match { @@ -546,8 +546,7 @@ class ModularProductProgramPass extends RewritePass { andCondition = Operator.and(andCondition, checkActVarCondition) } val renamedExpression = getRenamedExpr(expr, context, k) - val renamedParams = params.map(p => getRenamedExpr(p, context, k)) - val newAssertStatement = AssertStmt(renamedExpression, id, renamedParams) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -561,8 +560,7 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val renamedParams = params.map(p => getRenamedExpr(p, context, i)) - val newAssertStatement = AssertStmt(renamedExpression, id, params) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -578,8 +576,7 @@ class ModularProductProgramPass extends RewritePass { val currentActVarName = activationVariableArray(i) val checkActVarCondition = currentActVarName.asInstanceOf[Expr] val renamedExpression = getRenamedExpr(expr, context, i) - val renamedParams = params.map(p => getRenamedExpr(p, context, i)) - val newAssertStatement = AssertStmt(renamedExpression, id, params) + val newAssertStatement = AssertStmt(renamedExpression, id, decorators) ASTNode.introducePos(true, true, newAssertStatement, stmts.head.position) val trueBlockStmt = BlockStmt(emptyVarsList,List(newAssertStatement.asInstanceOf[Statement])) val falseBlockStmt = BlockStmt(emptyVarsList, List(SkipStmt())) @@ -1097,7 +1094,7 @@ class ModularProductProgramPass extends RewritePass { newstmts += oldstmts.head } - case AssertStmt(expr, id, params) => + case AssertStmt(expr, id, decorators) => expr match { case OperatorApplication(op, operands) => val hasHyperSelect = isHyperSelectPresent(op, operands) diff --git a/src/main/scala/uclid/lang/ModuleTypeChecker.scala b/src/main/scala/uclid/lang/ModuleTypeChecker.scala index 5917d55fc..9fdeeb51c 100644 --- a/src/main/scala/uclid/lang/ModuleTypeChecker.scala +++ b/src/main/scala/uclid/lang/ModuleTypeChecker.scala @@ -50,7 +50,7 @@ class ModuleTypeCheckerPass extends ReadOnlyPass[Set[ModuleError]] in } else { st match { - case AssertStmt(e, id, params) => + case AssertStmt(e, id, _) => val eType = exprTypeChecker.typeOf(e, context) if (!eType.isBool) { in + ModuleError("Assertion expression must be of Boolean or Temporal type", st.position) diff --git a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala index 1244a5234..09777d377 100644 --- a/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala +++ b/src/main/scala/uclid/lang/PrimedAssignmentChecker.scala @@ -100,11 +100,11 @@ class PrimedAssignmentCheckerPass extends ReadOnlyPass[Set[ModuleError]] } } st match { - case IfElseStmt(_, _, _) | - ForStmt(_, _, _, _) | WhileStmt(_, _, _) | - CaseStmt(_) | SkipStmt() | - AssertStmt(_, _, _) | AssumeStmt(_, _) | - HavocStmt(_) | BlockStmt(_, _) => + case IfElseStmt(_, _, _) | + ForStmt(_, _, _, _) | WhileStmt(_, _, _) | + CaseStmt(_) | SkipStmt() | + AssertStmt(_, _, _) | AssumeStmt(_, _) | + HavocStmt(_) | BlockStmt(_, _) => in case ModuleCallStmt(_) => checkParallelConstruct("next") diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 1c495acb3..08ea601a9 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -846,17 +846,19 @@ case object CoverDecorator extends ExprDecorator { override val hashId = 2605 override val md5hashCode = computeMD5Hash } +case object SATOnlyDecorator extends ExprDecorator { + override def toString = "satonly" + override val hashId = 2606 + override val md5hashCode = computeMD5Hash +} object ExprDecorator { /** Factory constructor. */ def parse(e : Expr) : ExprDecorator = { val dec = e match { - case Identifier(id) => - if (id == "LTL") { - LTLExprDecorator - } else { - UnknownDecorator(e.toString) - } + case Identifier("LTL") => LTLExprDecorator + case Identifier("cover") => CoverDecorator + case Identifier("SATOnly") => SATOnlyDecorator case _ => UnknownDecorator(e.toString) } dec.pos = e.pos @@ -1152,14 +1154,15 @@ case class SkipStmt() extends Statement { override val hashId = 3000 override val md5hashCode = computeMD5Hash } -case class AssertStmt(e: Expr, id : Option[Identifier], params: List[Expr]) extends Statement { +case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDecorator]) extends Statement { override def toLines = { - val paramStr = if (params.size > 0) { - " [" + Utils.join(params.map(_.toString()), ", ") + "] " + val name = "assert" + val decoratorStr = if (decorators.size > 0) { + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] " } else { "" } val prefix = id match { - case Some(n) => "assert " + n.toString() + paramStr + ": " - case None => "assert " + paramStr + case Some(n) => name + " " + n.toString() + decoratorStr + ": " + case None => name + " " + decoratorStr } List(prefix + e.toString() + "; // " + position.toString) } diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 16e19f355..34be86921 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -460,6 +460,10 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Statement: PackratParser[Statement] = positioned { KwSkip <~ ";" ^^ { case _ => SkipStmt() } | KwAssert ~> Expr <~ ";" ^^ { case e => AssertStmt(e, None, List.empty) } | + KwAssert ~> ("[" ~> IdList <~ "]" ~ ":") ~ Expr <~ ";" ^^ { + case ids ~ e => + AssertStmt(e, None, ids.map(ExprDecorator.parse(_))) + } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ diff --git a/test/debug/mc.ucl b/test/debug/mc.ucl new file mode 100644 index 000000000..c50871535 --- /dev/null +++ b/test/debug/mc.ucl @@ -0,0 +1,17 @@ +module main{ + var arr_1: [integer]boolean; + var n_1: integer; + + init { + assert [cover, SATOnly]: + (true && (n_1 > 0) ==> + (forall (i : integer) :: (((i < 0) || (i >= n_1)) ==> !((arr_1)[i]))) && + (exists (i : integer) :: (((0 <= i) && (i < n_1)) && (arr_1)[i]))); + } + + control { + v = unroll(1); + check; + print_results; + } +} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl new file mode 100644 index 000000000..038d4808c --- /dev/null +++ b/test/modelcounter/counting_2n.ucl @@ -0,0 +1,19 @@ +module main { + + define U(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + define V(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + define W(arr : [integer]boolean, n : integer) : boolean = + (n > 0) && (forall (i : integer) :: !arr[i]); + + proof { + assert disjoint: #[(n : integer) for (arr: [integer]boolean)] :: (U(arr, n)) == + #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) + + #[(n : integer) for (arr: [integer]boolean)] :: (W(arr, n)); + assert constLB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) >= 1; + // assert constUB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) < 2; + } +} + From f77c40ed0fc1dc4c3c6e332fd07750f3d358ca8d Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 18 Apr 2020 23:45:58 +0530 Subject: [PATCH 081/119] New rewriter. --- .../extensions/modelcounts/UMCLanguage.scala | 102 ++++++++++++++++-- .../extensions/modelcounts/UMCParser.scala | 43 +++++++- .../extensions/modelcounts/UMCRewriter.scala | 60 ++++++----- .../scala/uclid/lang/ASTVisitorUtils.scala | 1 + src/main/scala/uclid/lang/UclidParser.scala | 2 +- test/modelcounter/counting_2n.ucl | 29 ++--- 6 files changed, 184 insertions(+), 53 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 304d0a872..af0b17ef5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -37,9 +37,11 @@ */ package uclid.extensions.modelcounts + import uclid.UclidMain import uclid.{lang => l} import uclid.Utils +import uclid.Memo /** CountingOp is a new operator we introduce for the UMC extension. */ @@ -58,21 +60,44 @@ case class CountingOp(xs: List[(l.Identifier, l.Type)], ys: List[(l.Identifier, override val md5hashCode = computeMD5Hash(xs, ys) } + /** This is the base class for all the "statements" in the proof. */ sealed abstract class Statement extends l.ASTNode { override def toString = Utils.join(toLines, "\n") + "\n" def toLines : List[String] - def countingOps : Seq[CountingOp] - def expressions: Seq[l.Expr] + val countingOps : Seq[CountingOp] + val expressions: Seq[l.Expr] def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] } +case class AssertStmt(e : l.Expr) extends Statement { + override val hashId = 130000 + override val md5hashCode = computeMD5Hash(e) + override def toLines = List("assert " + e.toString()) + override val countingOps = { + def isCountingOp(e : l.Expr) = { + e match { + case CountingOp(_, _, _) => true + case _ => false + } + } + UMCExpressions.findSubExpressions(e, isCountingOp _).map(_.asInstanceOf[CountingOp]).toSeq + } + override val expressions = Seq(e) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + rewriter(e) match { + case Some(eP) => Some(AssertStmt(eP)) + case None => None + } + } +} + case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) - override def countingOps = Seq(e1, e2, e3) - override def expressions = Seq(e1, e2, e3) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { case (Some(e1p), Some(e2p), Some(e3p)) => @@ -120,8 +145,8 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { override val hashId = 130002 override val md5hashCode = computeMD5Hash(op, cnt) override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) - override def countingOps = Seq(op) - override def expressions = Seq(op, cnt) + override val countingOps = Seq(op) + override val expressions = Seq(op, cnt) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(op.e), rewriter(cnt)) match { case (Some(ep), Some(cntp)) => @@ -135,8 +160,8 @@ case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { override val hashId = 130003 override val md5hashCode = computeMD5Hash(e, v) override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) - override def countingOps = Seq(e) - override def expressions = Seq(e, v) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e.e), rewriter(v)) match { case (Some(e1p), Some(e2p)) => @@ -151,8 +176,8 @@ case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { override val hashId = 130004 override val md5hashCode = computeMD5Hash(e, v) override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) - override def countingOps = Seq(e) - override def expressions = Seq(e, v) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { (rewriter(e.e), rewriter(v)) match { case (Some(e1p), Some(e2p)) => @@ -260,4 +285,61 @@ object UMCExpressions { l.OperatorApplication(l.DistinctOp(), es) } } + + def _findSubExpressions(e : l.Expr, fn : l.Expr => Boolean) : Set[l.Expr] = + findSubExpressions((e, fn)) + + val findSubExpressions = new Memo[(l.Expr, l.Expr => Boolean), Set[l.Expr]]( + (p : (l.Expr, l.Expr => Boolean)) => { + val expr = p._1 + val fn = p._2 + val subExprs : Set[l.Expr] = expr match { + case _ : l.Literal => Set.empty + case _ : l.Identifier => Set.empty + case _ : l.ExternalIdentifier => Set.empty + case l.ConstArray(e, t) => _findSubExpressions(e, fn) + case l.Tuple(es) => es.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.OperatorApplication(op, args) => + args.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.Lambda(ids, e) => _findSubExpressions(e, fn) + case l.FuncApplication(e1, e2) => e2.foldLeft(_findSubExpressions(e1, fn))((acc, e) => acc ++ _findSubExpressions(e, fn)) + case CountingOp(xs, ys, e) => _findSubExpressions(e, fn) + } + if (fn(expr)) { subExprs + expr} + else { subExprs } + } + ) } + +class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { + val rewrite : Memo[l.Expr, l.Expr] = new Memo[l.Expr, l.Expr]( + exp => { + val expP : l.Expr = exp match { + case _ : l.Literal | _ : l.Identifier | _ : l.ExternalIdentifier => + exp + case l.ConstArray(e, t) => + val eP : l.Expr = rewrite(e) + l.ConstArray(eP, t) + case l.Tuple(es) => + val esP = es.map(e => rewrite(e)) + l.Tuple(esP) + case l.OperatorApplication(op, args) => + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + case l.Lambda(ids, exp) => + val expP = rewrite(exp) + l.Lambda(ids, exp) + case l.FuncApplication(e1, e2s) => + val e1p = rewrite(e1) + val e2sp = e2s.map(e2 => rewrite(e2)) + l.FuncApplication(e1p, e2sp) + case CountingOp(xs, ys, e) => + val eP = rewrite(e) + CountingOp(xs, ys, eP) + } + rwMap.get(expP) match { + case Some(repl) => repl + case None => expP + } + }) +} \ No newline at end of file diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 300386934..1cbcdb48b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -71,7 +71,24 @@ class UMCParser extends l.UclidParser { } } - lazy val AssertStmt: PackratParser[Statement] = positioned { + lazy val C0: PackratParser[l.Expr] = positioned { CountingExpr | E1 } + + override lazy val E15: PackratParser[l.Expr] = positioned { + Literal | + "{" ~> Expr ~ rep("," ~> Expr) <~ "}" ^^ {case e ~ es => l.Tuple(e::es)} | + KwIf ~> ("(" ~> Expr <~ ")") ~ (KwThen ~> Expr) ~ (KwElse ~> Expr) ^^ { + case expr ~ thenExpr ~ elseExpr => l.OperatorApplication(l.ITEOp(), List(expr, thenExpr, elseExpr)) + } | + ConstArray | + KwLambda ~> (IdTypeList) ~ ("." ~> Expr) ^^ { case idtyps ~ expr => l.Lambda(idtyps, expr) } | + "(" ~> CExpr <~ ")" | + Id <~ OpPrime ^^ { case id => l.OperatorApplication(l.GetNextValueOp(), List(id)) } | + Id + } + + lazy val CExpr: PackratParser[l.Expr] = positioned { C0 } + + lazy val Stmt: PackratParser[Statement] = positioned { KwAssert ~ KwDisjoint ~ ":" ~> (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) @@ -90,10 +107,13 @@ class UMCParser extends l.UclidParser { case e ~ v => { ConstUbStmt(e, v) } + } | + KwAssert ~> CExpr <~ ";" ^^ { + case e => AssertStmt(e) } } lazy val ProofStmt: PackratParser[Statement] = - positioned ( AssertStmt ); + positioned ( Stmt ); lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } @@ -114,9 +134,26 @@ class UMCParser extends l.UclidParser { } } +class RewriteDefines extends l.RewriteDefines { + override def visitExpr(e : l.Expr, context : l.Scope) : Option[l.Expr] = { + val eP = (e match { + case i : Identifier => visitIdentifier(i, context) + case eId : l.ExternalIdentifier => visitExternalIdentifier(eId, context) + case lit : l.Literal => visitLiteral(lit, context) + case rec : l.Tuple => visitTuple(rec, context) + case opapp : l.OperatorApplication => visitOperatorApp(opapp, context) + case a : l.ConstArray => visitConstArray(a, context) + case fapp : l.FuncApplication => visitFuncApp(fapp, context) + case lambda : l.Lambda => visitLambda(lambda, context) + case cntOp : CountingOp => + visitExpr(cntOp.e, context).flatMap(eP => Some(CountingOp(cntOp.xs, cntOp.ys, eP))) + }).flatMap(pass.rewriteExpr(_, context)) + return l.ASTNode.introducePos(true, true, eP, e.position) + } +} object UMCParser { val parserObj = new UMCParser() - val rewriter = new l.RewriteDefines() + val rewriter = new RewriteDefines() def parseUMCModel(file: java.io.File) : CountingProof = { val filePath = file.getPath() val text = scala.io.Source.fromFile(filePath).mkString diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index c5c62f063..98804d3eb 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -42,7 +42,9 @@ import uclid.{lang => l} import uclid.Memo import uclid.extensions.modelcounts.{UMCExpressions => E} import scala.collection.mutable.{Set => MutableSet, Map => MutableMap} - +import uclid.lang.BlockStmt + + class UMCRewriter(cntProof : CountingProof) { /* We will be using this set in a number of places. */ type CountingOpSet = Set[CountingOp] @@ -125,6 +127,14 @@ class UMCRewriter(cntProof : CountingProof) { def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } + + def rewriteAssert(ufMap : UFMap, st : AssertStmt) : List[l.Statement] = { + val rewriter = new ExprRewriter(ufMap.map(p => (p._1 -> _apply(p._2))).toMap) + val eP = rewriter.rewrite(st.e) + val assertStmt = l.AssertStmt(eP, None, List.empty) + List(assertStmt) + } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { val o1 = st.e1 val o2 = st.e2 @@ -154,48 +164,44 @@ class UMCRewriter(cntProof : CountingProof) { List(assumeStmt, assertStmt) } - def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.e) - val argVars = args.map(a => a._1) - val cnt = st.v.value.toInt - val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) + def getCnstBoundStmt(ufMap : UFMap, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { + val cntArgs = e.xs + val argVars = cntArgs.map(a => a._1) + val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) - val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) - val existsVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten + val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(e.e, rwMap, l.Scope.empty)) + val newVars : List[(l.Identifier, l.Type)] = + argsListP.map(argsP => (cntArgs zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten ++ + e.ys + val blkDecls = newVars.map(p => l.BlockVarsDecl(List(p._1), p._2)) val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) - val query = E.exists(existsVars, E.and(conjunction, distincts)) - val assertStmt = l.AssertStmt(query, None, List(l.CoverDecorator, l.SATOnlyDecorator)) + val query = E.and(conjunction, distincts) + val assertStmt = l.AssertStmt(query, None, decorators) + BlockStmt(blkDecls, List(assertStmt)) + } + def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { + val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(args, E.ge(ufn, st.v)) + val assumeExpr = E.forall(extractCountingArgs(st.e), E.ge(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) + List(blkStmt, assumeStmt) } def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.e) - val argVars = args.map(a => a._1) - val cnt = st.v.value.toInt - val argsListP = (1 to cnt).map(i => args.map(a => generateId(a._1.name))) - val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) - val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(st.e.e, rwMap, l.Scope.empty)) - val vs : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (args zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten - val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] - val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) - val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] - val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) - val query = E.not(E.exists(vs, E.and(conjunction, distincts))) - val assertStmt = l.AssertStmt(query, None, List.empty) + val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(args, E.lt(ufn, st.v)) + val assumeExpr = E.forall(extractCountingArgs(st.e), E.lt(ufn, st.v)) val assumeStmt = l.AssumeStmt(assumeExpr, None) - List(assertStmt, assumeStmt) + List(blkStmt, assumeStmt) } def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { st match { + case a : AssertStmt => + rewriteAssert(ufmap, a) case d : DisjointStmt => rewriteDisjoint(ufmap, d) case r : RangeStmt => diff --git a/src/main/scala/uclid/lang/ASTVisitorUtils.scala b/src/main/scala/uclid/lang/ASTVisitorUtils.scala index 3469b5cd7..d386fe0ea 100644 --- a/src/main/scala/uclid/lang/ASTVisitorUtils.scala +++ b/src/main/scala/uclid/lang/ASTVisitorUtils.scala @@ -130,6 +130,7 @@ object ExprRewriter val rewriter = new ASTRewriter("", new ExprRewriterPass(rewrites)) rewriter.visitLhs(lhs, context).get } + } class ExprRewriter(name: String, rewrites : Map[Expr, Expr]) diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index 34be86921..ca1f33574 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -276,7 +276,7 @@ class UclidParser extends UclidTokenParsers with PackratParsers { lazy val Pattern : PackratParser[(lang.Identifier, List[List[lang.Expr]])] = Id ~ ("[" ~> PatternList <~ "]") ^^ { case id ~ pats => (id, pats) } - lazy val E1: PackratParser[Expr] = + lazy val E1: PackratParser[Expr] = KwForall ~> IdTypeList ~ Pattern.? ~ ("::" ~> E1) ^^ { case ids ~ pat ~ expr => { pat match { diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 038d4808c..9c60eb551 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -1,19 +1,24 @@ module main { - define U(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); - define V(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); - define W(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: !arr[i]); + define U(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + define V(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + define W(arr : [integer]boolean, n : integer) : boolean = + (forall (i : integer) :: !arr[i]); proof { - assert disjoint: #[(n : integer) for (arr: [integer]boolean)] :: (U(arr, n)) == - #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) + - #[(n : integer) for (arr: [integer]boolean)] :: (W(arr, n)); - assert constLB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) >= 1; - // assert constUB: #[(n : integer) for (arr: [integer]boolean)] :: (V(arr, n)) < 2; + assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == + #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); + assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; + assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; + assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= + #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * + #[(i : integer) for (n : integer)] :: (0 <= i < 2) + using arr[n -> i]; } } From 100792eda387b661e3b30e9be7686fb662d34e57 Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 18 Apr 2020 23:54:22 +0530 Subject: [PATCH 082/119] a few minor additions to counting_2n. --- test/modelcounter/counting_2n.ucl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 9c60eb551..96067a266 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -7,18 +7,27 @@ module main { (exists (i : integer) :: 0 <= i < n && arr[i]); define W(arr : [integer]boolean, n : integer) : boolean = (forall (i : integer) :: !arr[i]); + define X(i : integer) : boolean = + (0 <= i < 2); proof { assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); + // #arr: W(arr, n) == 1 assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + // prove #i: X(i) == 2 + assert constLB: #[(i : integer) for (n : integer)] :: (X(i)) >= 2; + assert constUB: #[(i : integer) for (n : integer)] :: (X(i)) < 3; + assert forall (i : integer, n : integer) :: (#[(i : integer) for (n : integer)] :: (X(i))) == 2; + /* assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * #[(i : integer) for (n : integer)] :: (0 <= i < 2) using arr[n -> i]; + */ } } From 8f2e1f1f587f9698fecc6e12b5aed2cb69d3f3ba Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 20 Apr 2020 03:04:29 +0530 Subject: [PATCH 083/119] adding the indLb rule which seems to work for now. --- .../extensions/modelcounts/UMCLanguage.scala | 169 +++++++++++++++++- .../extensions/modelcounts/UMCParser.scala | 15 +- .../extensions/modelcounts/UMCRewriter.scala | 67 ++++++- test/modelcounter/counting_2n.ucl | 35 ++-- 4 files changed, 259 insertions(+), 27 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index af0b17ef5..fad82374a 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -93,6 +93,9 @@ case class AssertStmt(e : l.Expr) extends Statement { } case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + assert (e1.xs == e2.xs && e2.xs == e3.xs) + assert (e1.ys == e2.ys && e2.ys == e3.ys) + override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) @@ -188,6 +191,53 @@ case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { } } +case class ConstEqStmt(e : CountingOp, v : l.IntLit) extends Statement { + override val hashId = 130005 + override val md5hashCode = computeMD5Hash(e, v) + override def toLines = List("assert constEq: " + e.toString() + " >= " + v.toString()) + override val countingOps = Seq(e) + override val expressions = Seq(e, v) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e.e), rewriter(v)) match { + case (Some(e1p), Some(e2p)) => + val e1 = CountingOp(e.xs, e.ys, e1p) + Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit])) + case _ => None + } + } +} + +case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys(0)._2.isInt) + assert (fp.ys == f.ys && f.ys == g.ys) + + val n = fp.ys(0)._1 + + override val hashId = 130006 + override val md5hashCode = computeMD5Hash(fp, f, g, skolems) + override def toLines = { + List("assert indLB: " + fp.toString + " >= " + + f.toString() + " * " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + } + override val countingOps = Seq(fp, f, g) + override val expressions = Seq(fp, f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val e_fp = rewriter(fp.e) + val e_f = rewriter(f.e) + val e_g = rewriter(g.e) + val skolemsP = skolems.map(rewriter(_)).flatten + (e_fp, e_f, e_g) match { + case (Some(e1), Some(e2), Some(e3)) => + val fpNew = CountingOp(fp.xs, fp.ys, e1) + val fNew = CountingOp(f.xs, f.ys, e2) + val gNew = CountingOp(g.xs, g.ys, e3) + Some(IndLbStmt(fpNew, fNew, gNew, skolemsP)) + case _ => None + } + } +} case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + @@ -225,14 +275,27 @@ object UMCExpressions { l.OperatorApplication(l.ConjunctionOp(), List(e1, e2)) } + def andL(es : Seq[l.Expr]) = { + assert (es.size >= 1) + es.foldLeft(l.BoolLit(true).asInstanceOf[l.Expr])((acc, e) => and(acc, e)) + } + def or(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.DisjunctionOp(), List(e1, e2)) } + + def orL(es : Seq[l.Expr]) = { + assert (es.size >= 1) + es.foldLeft(l.BoolLit(false).asInstanceOf[l.Expr])((acc, e) => or(acc, e)) + } def iff(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IffOp(), List(e1, e2)) } + def implies(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.ImplicationOp(), List(e1, e2)) + } def not(e : l.Expr) = { l.OperatorApplication(l.NegationOp(), List(e)) } @@ -249,6 +312,10 @@ object UMCExpressions { l.OperatorApplication(l.SubOp(), List(e1, e2)) } + def mul(e1 : l.Expr, e2 : l.Expr) = { + l.OperatorApplication(l.MulOp(), List(e1, e2)) + } + def le(e1 : l.Expr, e2 : l.Expr) = { l.OperatorApplication(l.IntLEOp(), List(e1, e2)) } @@ -278,13 +345,17 @@ object UMCExpressions { ite(lt(e1, e2), e2, e1) } - def distinct(es : List[l.Expr]) = { + def distinct(es : l.Expr*) = { if (es.size <= 1) { l.BoolLit(true) } else { - l.OperatorApplication(l.DistinctOp(), es) + l.OperatorApplication(l.DistinctOp(), es.toList) } } + + def apply(id : l.Identifier, args : List[l.Expr]) = { + l.FuncApplication(id, args) + } def _findSubExpressions(e : l.Expr, fn : l.Expr => Boolean) : Set[l.Expr] = findSubExpressions((e, fn)) @@ -300,7 +371,15 @@ object UMCExpressions { case l.ConstArray(e, t) => _findSubExpressions(e, fn) case l.Tuple(es) => es.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) case l.OperatorApplication(op, args) => - args.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + val opExprs = op match { + case l.ArraySelect(inds) => + inds.foldLeft(Set.empty[l.Expr])((acc, e) => _findSubExpressions(e, fn) ++ acc) + case l.ArrayUpdate(inds, value) => + inds.foldLeft(_findSubExpressions(value, fn))((acc, e) => _findSubExpressions(e, fn) ++ acc) + case _ => + Set.empty[l.Expr] + } + args.foldLeft(opExprs)((acc, e) => _findSubExpressions(e, fn) ++ acc) case l.Lambda(ids, e) => _findSubExpressions(e, fn) case l.FuncApplication(e1, e2) => e2.foldLeft(_findSubExpressions(e1, fn))((acc, e) => acc ++ _findSubExpressions(e, fn)) case CountingOp(xs, ys, e) => _findSubExpressions(e, fn) @@ -309,9 +388,52 @@ object UMCExpressions { else { subExprs } } ) + + def findSupport(es : Seq[l.Expr]) : Set[l.Identifier] = { + es.foldLeft(Set.empty[l.Identifier])((acc, e) => acc ++ findSupport(e)) + } + val findSupport : Memo[l.Expr, Set[l.Identifier]] = new Memo[l.Expr, Set[l.Identifier]]( + e => { + e match { + case _ : l.Literal | _ : l.Identifier => + Set.empty + case l.ConstArray(e, t) => + findSupport(e) + case l.Tuple(es) => + findSupport(es) + case l.OperatorApplication(op, args) => + val subSupport = findSupport(args) + op match { + case qOp : l.QuantifiedBooleanOperator => + subSupport -- qOp.variables.map(_._1).toSet + case l.ArraySelect(inds) => + findSupport(inds) + case l.ArrayUpdate(inds, value) => + findSupport(inds) ++ findSupport(value) ++ subSupport + case _ => + subSupport + } + case l.Lambda(ids, exp) => + val subSupport = findSupport(exp) + subSupport -- ids.map(_._1).toSet + case CountingOp(xs, ys, e) => + // FIXME: this is a major hack. + // Introducing this because we *want* the variables in a CountingOp + // to be "captured" by outer quantifiers. + // Need to revist and fix this. + Set.empty + case _ : l.ExternalIdentifier => + throw new Utils.AssertionError("Eliminate external identifiers before calling findSupport.") + case l.FuncApplication(e1, e2s) => + throw new Utils.AssertionError("Eliminate function applications before calling findSupport.") + } + } + ) } -class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { +class ExprRewriter(val rwMap : Map[l.Expr, l.Expr]) { + val supports = rwMap.map(p => p._1 -> UMCExpressions.findSupport(p._1)).toMap + val rewrite : Memo[l.Expr, l.Expr] = new Memo[l.Expr, l.Expr]( exp => { val expP : l.Expr = exp match { @@ -324,10 +446,43 @@ class ExprRewriter(rwMap : Map[l.Expr, l.Expr]) { val esP = es.map(e => rewrite(e)) l.Tuple(esP) case l.OperatorApplication(op, args) => - val argsP = args.map(arg => rewrite(arg)) - l.OperatorApplication(op, argsP) + op match { + case qOp : l.QuantifiedBooleanOperator => + val mapP = rwMap.filter(p => !qOp.variables.exists(v => supports(p._1).contains(v._1))) + if (mapP != rwMap) { + // have to eliminate the bound variables. + val rewriter = new ExprRewriter(mapP) + val argsP = args.map(arg => rewriter.rewrite(arg)) + l.OperatorApplication(op, argsP) + } else { + // do the usual + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + } + case l.ArraySelect(inds) => + val indsP = inds.map(ind => rewrite(ind)) + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(l.ArraySelect(indsP), argsP) + case l.ArrayUpdate(inds, e) => + val indsP = inds.map(ind => rewrite(ind)) + val eP = rewrite(e) + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(l.ArrayUpdate(indsP, eP), argsP) + case _ => + // do the usual. + val argsP = args.map(arg => rewrite(arg)) + l.OperatorApplication(op, argsP) + } case l.Lambda(ids, exp) => - val expP = rewrite(exp) + val mapP = rwMap.filter(p => !ids.exists(v => supports(p._1).contains(v._1))) + val expP = if (mapP != rwMap) { + // have to eliminate the bound variables. + val rewriter = new ExprRewriter(mapP) + rewriter.rewrite(exp) + } else { + // do the usual. + rewrite(exp) + } l.Lambda(ids, exp) case l.FuncApplication(e1, e2s) => val e1p = rewrite(e1) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1cbcdb48b..1abd4f7ae 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -51,8 +51,11 @@ class UMCParser extends l.UclidParser { lazy val KwDisjoint = "disjoint" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" + lazy val KwConstEq = "constEq" + lazy val KwIndLb = "indLB" + lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB) + lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl) @@ -108,6 +111,16 @@ class UMCParser extends l.UclidParser { ConstUbStmt(e, v) } } | + KwAssert ~ KwConstEq ~ ":" ~> CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { + case e ~ v => { + ConstEqStmt(e, v) + } + } | + KwAssert ~ KwIndLb ~ ":" ~> CountingExpr ~ (">=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ e3 ~ es => { + IndLbStmt(e1, e2, e3, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 98804d3eb..4460d4278 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -177,7 +177,7 @@ class UMCRewriter(cntProof : CountingProof) { val trueLit = l.BoolLit(true).asInstanceOf[l.Expr] val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] - val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList) + val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) val query = E.and(conjunction, distincts) val assertStmt = l.AssertStmt(query, None, decorators) BlockStmt(blkDecls, List(assertStmt)) @@ -198,8 +198,65 @@ class UMCRewriter(cntProof : CountingProof) { List(blkStmt, assumeStmt) } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { + // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) + val f = indlb.f + val g = indlb.g + val f_xn = f.e + val g_yn = g.e + val ante = E.and(f_xn, g_yn) + val nplus1 = E.plus(indlb.n, l.IntLit(1)) + val skSubs = (f.xs.map(_._1.asInstanceOf[l.Expr]) zip indlb.skolems).toMap + + (indlb.n.asInstanceOf[l.Expr] -> nplus1) + val conseq = new ExprRewriter(skSubs).rewrite(f_xn) + val impl = E.implies(ante, conseq) + val qVars = f.xs ++ g.xs ++ f.ys + val qOp = E.forall(qVars, impl) + val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + + // Now we want to show injectivity of the skolem: + // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) + // ==> skolem(x1, y1, n) != skolem(x2, y2, n) + val x1s = f.xs.map(p => generateId(p._1.toString())) + val x2s = f.xs.map(p => generateId(p._1.toString())) + val y1s = g.xs.map(p => generateId(p._1.toString())) + val y2s = g.xs.map(p => generateId(p._1.toString())) + val rwx1 = new ExprRewriter((f.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwy1 = new ExprRewriter((g.xs zip y1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((f.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwy2 = new ExprRewriter((g.xs zip y2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwxy1 = new ExprRewriter(rwx1.rwMap ++ rwy1.rwMap) + val rwxy2 = new ExprRewriter(rwx2.rwMap ++ rwy2.rwMap) + val f_x1n = rwx1.rewrite(f.e) + val g_y1n = rwy1.rewrite(g.e) + val f_x2n = rwx2.rewrite(f.e) + val g_y2n = rwy2.rewrite(g.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + val ydiff = E.orL((y1s zip y2s).map(p => E.distinct(p._1, p._2))) + val sk1s = indlb.skolems.map(sk => rwxy1.rewrite(sk)) + val sk2s = indlb.skolems.map(sk => rwxy2.rewrite(sk)) + val ante2 = E.andL(List(f_x1n, f_x2n, g_y1n, g_y2n, E.or(xdiff, ydiff))) + val skdiff = E.orL((sk1s zip sk2s).map(p => E.distinct(p._1, p._2))) + val impl2 = E.implies(ante2, skdiff) + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ + (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ + (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + + // Finally, we have to produce the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) + val assumpQVars = f.xs ++ g.xs ++ f.ys + val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) + val assumpStmt = l.AssumeStmt(E.forall(assumpQVars, geqExpr), None) + + List(liftAssertStmt, injAssertStmt, assumpStmt) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { - st match { + val newStmts : List[l.Statement] = st match { case a : AssertStmt => rewriteAssert(ufmap, a) case d : DisjointStmt => @@ -210,9 +267,15 @@ class UMCRewriter(cntProof : CountingProof) { rewriteConstLb(ufmap, lb) case ub : ConstUbStmt => rewriteConstUb(ufmap, ub) + case eq : ConstEqStmt => + rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v)) ++ + rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1))) + case indLb : IndLbStmt => + rewriteIndLb(ufmap, indLb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } + l.ASTNode.introducePos(true, true, newStmts, st.position) } lazy val controlBlock : List[l.GenericProofCommand] = List( diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 96067a266..eefce1a96 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -1,33 +1,34 @@ module main { + // U is the function V_f in the paper. define U(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + // V is the function V in the paper. define V(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); + (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i]); + // W is the function V_1 in the paper. define W(arr : [integer]boolean, n : integer) : boolean = - (forall (i : integer) :: !arr[i]); - define X(i : integer) : boolean = - (0 <= i < 2); + (n > 0) && (forall (i : integer) :: !arr[i]); + define X(b : boolean) : boolean = true; proof { + // The satisfying assignments to U are the disjoint union of the satisfying + // assignments to V and W. assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); - // #arr: W(arr, n) == 1 - assert constLB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) >= 1; - assert constUB: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) < 2; + // prove #arr: W(arr, n) == 1 + assert constEq: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; - // prove #i: X(i) == 2 - assert constLB: #[(i : integer) for (n : integer)] :: (X(i)) >= 2; - assert constUB: #[(i : integer) for (n : integer)] :: (X(i)) < 3; - assert forall (i : integer, n : integer) :: (#[(i : integer) for (n : integer)] :: (X(i))) == 2; - /* + // prove #i: X(b) == 2 + assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; + assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; + // prove the inductive step in the count. assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * - #[(i : integer) for (n : integer)] :: (0 <= i < 2) - using arr[n -> i]; - */ + #[(b : boolean) for (n : integer)] :: (X(b)) + skolems (arr[n -> b]); } } From 73ac002656f7702fe833da80ba6ccff76418bd7c Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Mon, 20 Apr 2020 23:40:07 +0530 Subject: [PATCH 084/119] first attempt at model count proof. --- .../extensions/modelcounts/UMCLanguage.scala | 71 ++++++++++++------- .../extensions/modelcounts/UMCParser.scala | 23 ++++-- .../extensions/modelcounts/UMCRewriter.scala | 40 +++++------ test/debug/mc2.ucl | 24 +++++++ test/debug/mc3.ucl | 19 +++++ test/modelcounter/counting_2n.ucl | 12 ++-- 6 files changed, 136 insertions(+), 53 deletions(-) create mode 100644 test/debug/mc2.ucl create mode 100644 test/debug/mc3.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index fad82374a..006a765e5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -159,49 +159,67 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { } } } -case class ConstLbStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstLbStmt(e : CountingOp, v : l.IntLit, assump: l.Expr) extends Statement { override val hashId = 130003 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constLB: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constLB: " + e.toString() + " >= " + v.toString()) + } else { + List("assert constLB: " + assump.toString() + " ==> " + e.toString() + " >= " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstLbStmt(e1, e2p.asInstanceOf[l.IntLit], aP)) case _ => None } } } -case class ConstUbStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstUbStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends Statement { override val hashId = 130004 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constUB: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constUB: " + e.toString() + " < " + v.toString()) + } else { + List("assert constUB: " + assump.toString() + " ==> " + e.toString() + " < " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstUbStmt(e1, e2p.asInstanceOf[l.IntLit], aP)) case _ => None } } } -case class ConstEqStmt(e : CountingOp, v : l.IntLit) extends Statement { +case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends Statement { override val hashId = 130005 - override val md5hashCode = computeMD5Hash(e, v) - override def toLines = List("assert constEq: " + e.toString() + " >= " + v.toString()) + override val md5hashCode = computeMD5Hash(e, v, assump) + override def toLines = { + if (assump == l.BoolLit(true)) { + List("assert constEq: " + e.toString() + " >= " + v.toString()) + } else { + List("assert constEq: " + assump.toString() + " ==> " + e.toString() + " == " + v.toString()) + } + } override val countingOps = Seq(e) - override val expressions = Seq(e, v) + override val expressions = Seq(e, v, assump) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(e.e), rewriter(v)) match { - case (Some(e1p), Some(e2p)) => + (rewriter(e.e), rewriter(v), rewriter(assump)) match { + case (Some(e1p), Some(e2p), Some(aP)) => val e1 = CountingOp(e.xs, e.ys, e1p) - Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit])) + Some(ConstEqStmt(e1, e2p.asInstanceOf[l.IntLit], assump)) case _ => None } } @@ -211,8 +229,8 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) assert (fp.ys(0)._2.isInt) assert (fp.ys == f.ys && f.ys == g.ys) - val n = fp.ys(0)._1 + assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) override val hashId = 130006 override val md5hashCode = computeMD5Hash(fp, f, g, skolems) @@ -262,11 +280,16 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S object UMCExpressions { // Helper functions to more easily construct expressions. def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - val op = l.ForallOp(vs, List.empty) - l.OperatorApplication(op, List(e)) + if (vs.size > 0) { + val op = l.ForallOp(vs, List.empty) + l.OperatorApplication(op, List(e)) + } else { + e + } } def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { + assert (vs.size > 0) val op = l.ExistsOp(vs, List.empty) l.OperatorApplication(op, List(e)) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1abd4f7ae..aa0a0a8a7 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -58,7 +58,7 @@ class UMCParser extends l.UclidParser { lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = - positioned (TypeDecl | DefineDecl) + positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { @@ -103,17 +103,32 @@ class UMCParser extends l.UclidParser { } | KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstLbStmt(e, v) + ConstLbStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstLB ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstLbStmt(e, v, assump) } } | KwAssert ~ KwConstUB ~ ":" ~> CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstUbStmt(e, v) + ConstUbStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstUB ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("<" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstUbStmt(e, v, assump) } } | KwAssert ~ KwConstEq ~ ":" ~> CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { case e ~ v => { - ConstEqStmt(e, v) + ConstEqStmt(e, v, l.BoolLit(true)) + } + } | + KwAssert ~ KwConstEq ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("==" ~> Integer) <~ ";" ^^ { + case assump ~ e ~ v => { + ConstEqStmt(e, v, assump) } } | KwAssert ~ KwIndLb ~ ":" ~> CountingExpr ~ (">=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 4460d4278..2a0cf47a1 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -120,10 +120,6 @@ class UMCRewriter(cntProof : CountingProof) { }.flatten.toList } - def extractCountingArgs(e : CountingOp) = { - e.xs ++ e.ys - } - def _apply(uf : l.FunctionDecl) = { l.FuncApplication(uf.id, uf.sig.args.map(_._1)) } @@ -139,7 +135,7 @@ class UMCRewriter(cntProof : CountingProof) { val o1 = st.e1 val o2 = st.e2 val o3 = st.e3 - val args = extractCountingArgs(o1) + val args = o1.xs ++ o1.ys val f1 = o1.e val f2 = o2.e val f3 = o3.e @@ -149,23 +145,24 @@ class UMCRewriter(cntProof : CountingProof) { val ufn1 = _apply(ufMap(o1)) val ufn2 = _apply(ufMap(o2)) val ufn3 = _apply(ufMap(o3)) - val assumeExpr = E.forall(args, E.eq(ufn1, E.plus(ufn2, ufn3))) + val assumeExpr = E.forall(st.e1.ys, E.eq(ufn1, E.plus(ufn2, ufn3))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(assertStmt, assumeStmt) } def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { - val args = extractCountingArgs(st.op) val ufn = _apply(ufMap(st.op)) - val assumeExpr = E.forall(args, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) + val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) - val assertExpr = E.forall(args, E.eq(ufn, st.cnt)) + val assertExpr = E.forall(st.op.ys, E.eq(ufn, st.cnt)) val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } - def getCnstBoundStmt(ufMap : UFMap, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { + def getCnstBoundStmt(ufMap : UFMap, assump : l.Expr, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { val cntArgs = e.xs + val qVars = e.ys + val ante = assump val argVars = cntArgs.map(a => a._1) val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) @@ -178,22 +175,22 @@ class UMCRewriter(cntProof : CountingProof) { val conjunction = exprs.foldLeft(trueLit)((acc, e) => E.and(acc, e)) val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) - val query = E.and(conjunction, distincts) + val query = E.forall(qVars, E.implies(ante, E.and(conjunction, distincts))) val assertStmt = l.AssertStmt(query, None, decorators) BlockStmt(blkDecls, List(assertStmt)) } def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { - val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) + val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator, l.SATOnlyDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(extractCountingArgs(st.e), E.ge(ufn, st.v)) + val assumeExpr = E.forall(st.e.ys, E.implies(st.assump, E.ge(ufn, st.v))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { - val blkStmt = getCnstBoundStmt(ufMap, st.e, st.v.value.toInt, List(l.CoverDecorator)) + val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) - val assumeExpr = E.forall(extractCountingArgs(st.e), E.lt(ufn, st.v)) + val assumeExpr = E.forall(st.e.ys, E.implies(st.assump, E.lt(ufn, st.v))) val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } @@ -248,11 +245,14 @@ class UMCRewriter(cntProof : CountingProof) { val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) - val assumpQVars = f.xs ++ g.xs ++ f.ys val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) - val assumpStmt = l.AssumeStmt(E.forall(assumpQVars, geqExpr), None) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) + + val ufpn = _apply(ufMap(indlb.fp)) + val eqExpr = E.eq(ufnplus1, ufpn) + val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - List(liftAssertStmt, injAssertStmt, assumpStmt) + List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { @@ -268,8 +268,8 @@ class UMCRewriter(cntProof : CountingProof) { case ub : ConstUbStmt => rewriteConstUb(ufmap, ub) case eq : ConstEqStmt => - rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v)) ++ - rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1))) + rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v, eq.assump)) ++ + rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1), eq.assump)) case indLb : IndLbStmt => rewriteIndLb(ufmap, indLb) case _ => diff --git a/test/debug/mc2.ucl b/test/debug/mc2.ucl new file mode 100644 index 000000000..919c8d01e --- /dev/null +++ b/test/debug/mc2.ucl @@ -0,0 +1,24 @@ + module main { + define X (b: boolean): boolean = true; + function count_1(n: integer): integer; // line 0 + axiom assump_1 : (forall (n : integer) :: (count_1(n) >= 0)); // + function count_2(n: integer): integer; // line 0 + axiom assump_2 : (forall (n : integer) :: (count_2(n) >= 0)); // + + procedure countingProof() returns () + { + var arr : [integer]boolean; // line 22 + var n : integer; // line 22 + assert [cover, SATOnly]: + forall (n : integer) :: + (n > 0 && + (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (exists (i : integer) :: 0 <= i < n && arr[i])); + } + control { + v = verify(countingProof /* countingProof*/); // line 0 + check; // line 0 + print_results; // line 0 + } + } + diff --git a/test/debug/mc3.ucl b/test/debug/mc3.ucl new file mode 100644 index 000000000..3ad825520 --- /dev/null +++ b/test/debug/mc3.ucl @@ -0,0 +1,19 @@ + +module main{ + init { + var arr_2 : [integer]boolean; // line 16 + var arr_3 : [integer]boolean; // line 16 + var n : integer; // line 16 + assert[cover]: + (((true && (forall (i : integer) :: !((arr_2)[i]))) && + (forall (i : integer) :: !((arr_3)[i]))) && + distinct({arr_2}, {arr_3})); // line 0 + + } + + control { + v = unroll(1); + check; + print_results; + } +} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index eefce1a96..867527c22 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -2,14 +2,14 @@ module main { // U is the function V_f in the paper. define U(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); + (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); // V is the function V in the paper. define V(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && + (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && (exists (i : integer) :: 0 <= i < n && arr[i]); // W is the function V_1 in the paper. define W(arr : [integer]boolean, n : integer) : boolean = - (n > 0) && (forall (i : integer) :: !arr[i]); + (n >= 0) && (forall (i : integer) :: !arr[i]); define X(b : boolean) : boolean = true; proof { @@ -19,12 +19,14 @@ module main { #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); // prove #arr: W(arr, n) == 1 - assert constEq: #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; - assert forall (n : integer) :: (#[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n))) == 1; + assert constEq: (n >= 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; + // prove #arr: U(arr, 1) == 1 + assert constEq: (n == 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == 1; // prove #i: X(b) == 2 assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; // prove the inductive step in the count. + // #arr. U(arr, n + 1) >= #arr. U(arr, n) * 2 assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * #[(b : boolean) for (n : integer)] :: (X(b)) From d6c6459a79b1c3c9abf5aafecff1b1d7883f82ab Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Wed, 22 Apr 2020 21:17:41 +0530 Subject: [PATCH 085/119] Compilation fix after merge. --- src/main/scala/uclid/lang/Scope.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/lang/Scope.scala b/src/main/scala/uclid/lang/Scope.scala index 91b1b9a0e..94f394537 100644 --- a/src/main/scala/uclid/lang/Scope.scala +++ b/src/main/scala/uclid/lang/Scope.scala @@ -256,7 +256,7 @@ case class Scope ( case SharedVarsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.SharedVar(id, typ))) case ConstantLitDecl(id, lit) => Scope.addToMap(map, Scope.ConstantLit(id, lit)) case ConstantsDecl(ids, typ) => ids.foldLeft(map)((acc, id) => Scope.addToMap(acc, Scope.ConstantVar(id, typ))) - case GrammarDecl(id, sig, _) => Scope.addToMap(map, Scope.Grammar(id, sig.typ)) + case GrammarDecl(id, sig, nts) => Scope.addToMap(map, Scope.Grammar(id, sig.typ, nts)) case FunctionDecl(id, sig) => Scope.addToMap(map, Scope.Function(id, sig.typ)) case SynthesisFunctionDecl(id, sig, _, _, _) => Scope.addToMap(map, Scope.Function(id, sig.typ)) // FIXME case DefineDecl(id, sig, expr) => Scope.addToMap(map, Scope.Define(id, sig.typ, DefineDecl(id, sig, expr))) From 97105bb8deba74607b420ed4829c7b6fb91da1ae Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 8 May 2020 13:19:53 +0530 Subject: [PATCH 086/119] fixed disjoint to be or rule --- .../extensions/modelcounts/UMCLanguage.scala | 14 +++--- .../extensions/modelcounts/UMCMain.scala | 1 + .../extensions/modelcounts/UMCParser.scala | 14 +++--- .../extensions/modelcounts/UMCRewriter.scala | 50 +++++++++---------- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 006a765e5..a565ac561 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -81,7 +81,7 @@ case class AssertStmt(e : l.Expr) extends Statement { case _ => false } } - UMCExpressions.findSubExpressions(e, isCountingOp _).map(_.asInstanceOf[CountingOp]).toSeq + UMCExpressions.findSubExpressions(e, isCountingOp).map(_.asInstanceOf[CountingOp]).toSeq } override val expressions = Seq(e) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { @@ -92,13 +92,13 @@ case class AssertStmt(e : l.Expr) extends Statement { } } -case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { +case class OrStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { assert (e1.xs == e2.xs && e2.xs == e3.xs) assert (e1.ys == e2.ys && e2.ys == e3.ys) override val hashId = 130001 override val md5hashCode = computeMD5Hash(e1, e2, e3) - override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) + override def toLines = List("assert or: " + e1.toString() + " == " + e2.toString() + " + " + e3.toString()) override val countingOps = Seq(e1, e2, e3) override val expressions = Seq(e1, e2, e3) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { @@ -107,7 +107,7 @@ case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) exten val op1 = CountingOp(e1.xs, e1.ys, e1p) val op2 = CountingOp(e2.xs, e2.ys, e2p) val op3 = CountingOp(e3.xs, e3.ys, e3p) - Some(DisjointStmt(op1, op2, op3)) + Some(OrStmt(op1, op2, op3)) case _ => None } } @@ -118,8 +118,7 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { op.e match { case l.OperatorApplication(l.ConjunctionOp(), List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), - l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => - lb + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => lb case _ => throw new Utils.AssertionError("Unexpected operand to range expression.") } @@ -128,8 +127,7 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { op.e match { case l.OperatorApplication(l.ConjunctionOp(), List(l.OperatorApplication(l.LEOp(), List(lb, l.Identifier(v))), - l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => - ub + l.OperatorApplication(l.LTOp(), List(l.Identifier(u), ub)))) => ub case _ => throw new Utils.AssertionError("Unexpected operand to range expression.") } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 87661f2a6..45232ec69 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -61,6 +61,7 @@ object UMCMain { println("Parsed module: " + module.id.toString()) println(module.toString()) val moduleP = new UMCRewriter(module).process() + println("\nModule after rewriting: ") println(moduleP.toString()) runProcessedModel(moduleP) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index aa0a0a8a7..d51b00a81 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -48,19 +48,19 @@ import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { lazy val KwProof = "proof" - lazy val KwDisjoint = "disjoint" + lazy val KwOr = "or" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" lazy val KwConstEq = "constEq" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - - lexical.reserved += (KwProof, KwDisjoint, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) + + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) - lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = + lazy val CountingOpPrefix : PackratParser[(List[(Identifier, Type)], List[(Identifier, Type)])] = ("#[" ~> IdTypeList) ~ (KwFor ~> IdTypeList) <~ "]" ~ "::" ^^ { case xs ~ ys => (xs, ys) } | @@ -75,7 +75,7 @@ class UMCParser extends l.UclidParser { } lazy val C0: PackratParser[l.Expr] = positioned { CountingExpr | E1 } - + override lazy val E15: PackratParser[l.Expr] = positioned { Literal | "{" ~> Expr ~ rep("," ~> Expr) <~ "}" ^^ {case e ~ es => l.Tuple(e::es)} | @@ -92,9 +92,9 @@ class UMCParser extends l.UclidParser { lazy val CExpr: PackratParser[l.Expr] = positioned { C0 } lazy val Stmt: PackratParser[Statement] = positioned { - KwAssert ~ KwDisjoint ~ ":" ~> + KwAssert ~ KwOr ~ ":" ~> (CountingExpr <~ "==") ~ (CountingExpr <~ "+") ~ CountingExpr <~ ";" ^^ { - case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + case e1 ~ e2 ~ e3 => OrStmt(e1, e2, e3) } | KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { case e1 ~ e2 => { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2a0cf47a1..bbc2d90fa 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -60,7 +60,7 @@ class UMCRewriter(cntProof : CountingProof) { } /** Identifiers that are already declared in the module. */ - val existingIds = cntProof.decls.map(d => d.declNames).flatten.toSet + val existingIds = cntProof.decls.flatMap(d => d.declNames).toSet /** Identifiers that are declared + newly generated names. */ val usedIds : MutableSet[l.Identifier] = MutableSet.empty[l.Identifier] ++ existingIds /** Counters that track (roughly) the number of generated identifiers with each prefix. */ @@ -103,21 +103,21 @@ class UMCRewriter(cntProof : CountingProof) { l.OperatorApplication(l.IntGEOp(), List(e, l.IntLit(0))) } def quantify(ufDecl : l.FunctionDecl, e : l.Expr) : l.Expr = { - if (ufDecl.sig.args.size > 0) { + if (ufDecl.sig.args.nonEmpty) { l.OperatorApplication(l.ForallOp(ufDecl.sig.args, List.empty), List(e)) } else { e } } - ufMap.map { - p => { - val ufDecl = p._2 - val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) - val axExpr = quantify(ufDecl, innerExpr) - val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) - List(ufDecl, axiomDecl) - } - }.flatten.toList + ufMap.flatMap { + p => { + val ufDecl = p._2 + val innerExpr = geqZero(l.FuncApplication(ufDecl.id, ufDecl.sig.args.map(_._1))) + val axExpr = quantify(ufDecl, innerExpr) + val axiomDecl = l.AxiomDecl(Some(generateId("assump")), axExpr, List.empty) + List(ufDecl, axiomDecl) + } + }.toList } def _apply(uf : l.FunctionDecl) = { @@ -131,7 +131,7 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt) } - def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + def rewriteOr(ufMap : UFMap, st : OrStmt) : List[l.Statement] = { val o1 = st.e1 val o2 = st.e2 val o3 = st.e3 @@ -149,7 +149,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeStmt = l.AssumeStmt(assumeExpr, None) List(assertStmt, assumeStmt) } - + def rewriteRange(ufMap : UFMap, st : RangeStmt) : List[l.Statement] = { val ufn = _apply(ufMap(st.op)) val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) @@ -158,7 +158,7 @@ class UMCRewriter(cntProof : CountingProof) { val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } - + def getCnstBoundStmt(ufMap : UFMap, assump : l.Expr, e : CountingOp, cnt : Int, decorators : List[l.ExprDecorator]) = { val cntArgs = e.xs val qVars = e.ys @@ -167,7 +167,7 @@ class UMCRewriter(cntProof : CountingProof) { val argsListP = (1 to cnt).map(i => cntArgs.map(a => generateId(a._1.name))) val rwMaps = argsListP.map(argsP => (argVars.map(_.asInstanceOf[l.Expr]) zip argsP).toMap) val exprs = rwMaps.map(rwMap => l.ExprRewriter.rewriteExprOnce(e.e, rwMap, l.Scope.empty)) - val newVars : List[(l.Identifier, l.Type)] = + val newVars : List[(l.Identifier, l.Type)] = argsListP.map(argsP => (cntArgs zip argsP).map(p => (p._2, p._1._2)).toList).toList.flatten ++ e.ys val blkDecls = newVars.map(p => l.BlockVarsDecl(List(p._1), p._2)) @@ -186,7 +186,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeStmt = l.AssumeStmt(assumeExpr, None) List(blkStmt, assumeStmt) } - + def rewriteConstUb(ufMap : UFMap, st : ConstUbStmt) : List[l.Statement] = { val blkStmt = getCnstBoundStmt(ufMap, st.assump, st.e, st.v.value.toInt, List(l.CoverDecorator)) val ufn = _apply(ufMap(st.e)) @@ -210,10 +210,10 @@ class UMCRewriter(cntProof : CountingProof) { val qVars = f.xs ++ g.xs ++ f.ys val qOp = E.forall(qVars, impl) val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) - + // Now we want to show injectivity of the skolem: - // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) - // ==> skolem(x1, y1, n) != skolem(x2, y2, n) + // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) + // ==> skolem(x1, y1, n) != skolem(x2, y2, n) val x1s = f.xs.map(p => generateId(p._1.toString())) val x2s = f.xs.map(p => generateId(p._1.toString())) val y1s = g.xs.map(p => generateId(p._1.toString())) @@ -240,18 +240,18 @@ class UMCRewriter(cntProof : CountingProof) { (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) - + // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) - + val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - + List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } @@ -259,8 +259,8 @@ class UMCRewriter(cntProof : CountingProof) { val newStmts : List[l.Statement] = st match { case a : AssertStmt => rewriteAssert(ufmap, a) - case d : DisjointStmt => - rewriteDisjoint(ufmap, d) + case d : OrStmt => + rewriteOr(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) case lb : ConstLbStmt => @@ -298,7 +298,7 @@ class UMCRewriter(cntProof : CountingProof) { val countingOps = identifyCountOps(cntProof.stmts) val ufMap = generateCountingOpToUFMap(countingOps) val ufDecls = generateUFDecls(ufMap) - val newProofStmts = cntProof.stmts.map(st => rewriteAssert(ufMap, st)).flatten + val newProofStmts = cntProof.stmts.flatMap(st => rewriteAssert(ufMap, st)) val newProofProc = l.ProcedureDecl( l.Identifier("countingProof"), l.ProcedureSig(List.empty, List.empty), From 2caf963240cdf9fb7222ff85ed5032282dbc2d61 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 8 May 2020 13:27:28 +0530 Subject: [PATCH 087/119] updated the test example --- test/modelcounter/counting_2n.ucl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl index 867527c22..d270863e8 100644 --- a/test/modelcounter/counting_2n.ucl +++ b/test/modelcounter/counting_2n.ucl @@ -15,7 +15,7 @@ module main { proof { // The satisfying assignments to U are the disjoint union of the satisfying // assignments to V and W. - assert disjoint: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == + assert or: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); // prove #arr: W(arr, n) == 1 From afd8fa0b9a2a8447fa5933349bf7a716021fde4c Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 11:10:32 +0530 Subject: [PATCH 088/119] minor fix to example --- test/modelcounter/hello.ucl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/hello.ucl index a8dfe8849..95c10e6b9 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/hello.ucl @@ -9,7 +9,7 @@ module main { assert constLB: #[(n:integer)] :: (g(n)) >= 5; assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; - assert disjoint: #[(n:integer) for ()] :: (f(n)) == + assert or: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } From 4734acd995062d013ebf7ce7a799ee73c15dd9cc Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 11:12:01 +0530 Subject: [PATCH 089/119] added UB rule --- .../extensions/modelcounts/UMCLanguage.scala | 19 ++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 22 ++++++++++------ .../extensions/modelcounts/UMCRewriter.scala | 26 ++++++++++++++++--- test/modelcounter/test-UB.ucl | 9 +++++++ 4 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 test/modelcounter/test-UB.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index a565ac561..f61b1067e 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -254,6 +254,25 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } } + +case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { + override val hashId = 130007 + override val md5hashCode = computeMD5Hash(e1, e2) + override def toLines = + List("assert UB: " + e1.toString() + " <= " + e2.toString()) + override val countingOps = Seq(e1, e2) + override val expressions = Seq(e1, e2) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e)) match { + case (Some(e1p), Some(e2p)) => + val e1New = CountingOp(e1.xs, e1.ys, e1p) + val e2New = CountingOp(e2.xs, e2.ys, e2p) + Some(UbStmt(e1New, e2New)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index d51b00a81..4b9b4ad4d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -47,15 +47,16 @@ import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { - lazy val KwProof = "proof" - lazy val KwOr = "or" - lazy val KwConstLB = "constLB" - lazy val KwConstUB = "constUB" - lazy val KwConstEq = "constEq" - lazy val KwIndLb = "indLB" - lazy val KwSkolems = "skolems" + lazy val KwProof = "proof" + lazy val KwConstLB = "constLB" + lazy val KwConstUB = "constUB" + lazy val KwConstEq = "constEq" + lazy val KwUB = "UB" + lazy val KwOr = "or" + lazy val KwIndLb = "indLB" + lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -136,6 +137,11 @@ class UMCParser extends l.UclidParser { IndLbStmt(e1, e2, e3, es) } } | + KwAssert ~ KwUB ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) <~ ";" ^^ { + case e1 ~ e2 => { + UbStmt(e1, e2) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index bbc2d90fa..b29f430cd 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -195,6 +195,21 @@ class UMCRewriter(cntProof : CountingProof) { List(blkStmt, assumeStmt) } + def rewriteUb(ufMap : UFMap, st : UbStmt) : List[l.Statement] = { + val s1 = st.e1 + val s2 = st.e2 + val args = s1.xs ++ s1.ys + val f = s1.e + val g = s2.e + val assertExpr = E.forall(args, E.implies(f, g)) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, ufn2)) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -255,6 +270,7 @@ class UMCRewriter(cntProof : CountingProof) { List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { val newStmts : List[l.Statement] = st match { case a : AssertStmt => @@ -263,15 +279,17 @@ class UMCRewriter(cntProof : CountingProof) { rewriteOr(ufmap, d) case r : RangeStmt => rewriteRange(ufmap, r) - case lb : ConstLbStmt => - rewriteConstLb(ufmap, lb) - case ub : ConstUbStmt => - rewriteConstUb(ufmap, ub) + case constLb : ConstLbStmt => + rewriteConstLb(ufmap, constLb) + case constUb : ConstUbStmt => + rewriteConstUb(ufmap, constUb) case eq : ConstEqStmt => rewriteConstLb(ufmap, ConstLbStmt(eq.e, eq.v, eq.assump)) ++ rewriteConstUb(ufmap, ConstUbStmt(eq.e, l.IntLit(eq.v.value + 1), eq.assump)) case indLb : IndLbStmt => rewriteIndLb(ufmap, indLb) + case ub : UbStmt => + rewriteUb(ufmap, ub) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-UB.ucl b/test/modelcounter/test-UB.ucl new file mode 100644 index 000000000..4a0c822e3 --- /dev/null +++ b/test/modelcounter/test-UB.ucl @@ -0,0 +1,9 @@ +module main { + define f(n : integer) : boolean = 11 <= n < 21; + define g(n : integer) : boolean = 5 <= n < 21; + + proof { + assert UB: #[(n:integer)] :: (f(n)) <= #[(n:integer)] :: (g(n)); + } +} + From d696a7cd6ff348e2abe2b3609c5d05f2da380f28 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 14:18:31 +0530 Subject: [PATCH 090/119] minor edit: adding assert to UB rule - ensuring counting quantifier is applied on the same set of variables in each expression --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index f61b1067e..1e0cc6186 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -256,6 +256,8 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { + assert (e1.xs == e2.xs && e1.ys == e2.ys) + override val hashId = 130007 override val md5hashCode = computeMD5Hash(e1, e2) override def toLines = From c7e06501658a75ee0b43d9ec7d57ebcc300f15d6 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sat, 9 May 2020 17:44:19 +0530 Subject: [PATCH 091/119] added AndUB rule --- .../extensions/modelcounts/UMCLanguage.scala | 23 +++++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 7 +++++- .../extensions/modelcounts/UMCRewriter.scala | 22 ++++++++++++++++++ test/modelcounter/test-AndUB.ucl | 14 +++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 test/modelcounter/test-AndUB.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 1e0cc6186..643ee12d5 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -42,6 +42,7 @@ import uclid.UclidMain import uclid.{lang => l} import uclid.Utils import uclid.Memo +import uclid.lang.{Identifier, Type} /** CountingOp is a new operator we introduce for the UMC extension. */ @@ -275,6 +276,28 @@ case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { } } +case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + println("Reached here") + assert (e1.xs.toSet == (e2.xs.toSet | e1.xs.toSet)) + + println("Assertions passed") + override val hashId = 130008 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert andUB: " + e1.toString() + " <= " + e2.toString() + " * " + e3.toString()) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(AndUbStmt(op1, op2, op3)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 4b9b4ad4d..28cbda46d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -52,11 +52,12 @@ class UMCParser extends l.UclidParser { lazy val KwConstUB = "constUB" lazy val KwConstEq = "constEq" lazy val KwUB = "UB" + lazy val KwAndUB = "andUB" lazy val KwOr = "or" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -142,6 +143,10 @@ class UMCParser extends l.UclidParser { UbStmt(e1, e2) } } | + KwAssert ~ KwAndUB ~ ":" ~> + (CountingExpr <~ "<=") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { + case e1 ~ e2 ~ e3 => AndUbStmt(e1, e2, e3) + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index b29f430cd..a9eeb6288 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -210,6 +210,26 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteAndUb(ufMap : UFMap, st : AndUbStmt) : List[l.Statement] = { + val s1 = st.e1 + val s2 = st.e2 + val s3 = st.e3 + val args1 = s1.xs ++ s1.ys + val args2 = s2.xs ++ s2.ys + val args3 = s3.xs ++ s3.ys + val f1 = s1.e + val f2 = s2.e + val f3 = s3.e + val assertExpr = E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val ufn3 = _apply(ufMap(s3)) + val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, E.mul(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -290,6 +310,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteIndLb(ufmap, indLb) case ub : UbStmt => rewriteUb(ufmap, ub) + case andUb : AndUbStmt => + rewriteAndUb(ufmap, andUb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-AndUB.ucl b/test/modelcounter/test-AndUB.ucl new file mode 100644 index 000000000..0c1e08038 --- /dev/null +++ b/test/modelcounter/test-AndUB.ucl @@ -0,0 +1,14 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 5 <= n < 10; + define h(n : integer, m : integer) : boolean = (1 <= n < 10) && (5 <= m < 10); + + proof { + assert andUB: #[(n: integer, m : integer)] :: (h(n, m)) <= + #[(n: integer)] :: (f(n)) * + #[(m: integer)] :: (g(m)); + } +} + + + From 249d920ed41dfba3561f333197212b1fad681eb4 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 10 May 2020 16:50:30 +0530 Subject: [PATCH 092/119] added Disjoint rule --- .../extensions/modelcounts/UMCLanguage.scala | 24 ++++++++++++++--- .../extensions/modelcounts/UMCParser.scala | 8 +++++- .../extensions/modelcounts/UMCRewriter.scala | 26 +++++++++++++++++++ test/modelcounter/test-disjoint.ucl | 11 ++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 test/modelcounter/test-disjoint.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 643ee12d5..0ee4aecd2 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -277,10 +277,8 @@ case class UbStmt(e1 : CountingOp, e2: CountingOp) extends Statement { } case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { - println("Reached here") - assert (e1.xs.toSet == (e2.xs.toSet | e1.xs.toSet)) + assert (e1.xs.toSet == (e2.xs.toSet union e3.xs.toSet)) - println("Assertions passed") override val hashId = 130008 override val md5hashCode = computeMD5Hash(e1, e2, e3) override def toLines = List("assert andUB: " + e1.toString() + " <= " + e2.toString() + " * " + e3.toString()) @@ -298,6 +296,26 @@ case class AndUbStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends } } +case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Statement { + assert (e1.xs.toSet == (e2.xs.toSet union e3.xs.toSet)) + + override val hashId = 130009 + override val md5hashCode = computeMD5Hash(e1, e2, e3) + override def toLines = List("assert disjoint: " + e1.toString() + " == " + e2.toString() + " * " + e3.toString()) + override val countingOps = Seq(e1, e2, e3) + override val expressions = Seq(e1, e2, e3) + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + (rewriter(e1.e), rewriter(e2.e), rewriter(e3.e)) match { + case (Some(e1p), Some(e2p), Some(e3p)) => + val op1 = CountingOp(e1.xs, e1.ys, e1p) + val op2 = CountingOp(e2.xs, e2.ys, e2p) + val op3 = CountingOp(e3.xs, e3.ys, e3p) + Some(DisjointStmt(op1, op2, op3)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 28cbda46d..ef9ac50d6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -53,11 +53,13 @@ class UMCParser extends l.UclidParser { lazy val KwConstEq = "constEq" lazy val KwUB = "UB" lazy val KwAndUB = "andUB" + lazy val KwDisjoint = "disjoint" lazy val KwOr = "or" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" - lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB) + lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, + KwDisjoint) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -147,6 +149,10 @@ class UMCParser extends l.UclidParser { (CountingExpr <~ "<=") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => AndUbStmt(e1, e2, e3) } | + KwAssert ~ KwDisjoint ~ ":" ~> + (CountingExpr <~ "==") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { + case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index a9eeb6288..20c962e31 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -230,6 +230,30 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { + val e2s = st.e2.xs.toSet + val e3s = st.e3.xs.toSet + val intersection = if ((e2s intersect e3s).isEmpty) l.BoolLit(true) + else l.BoolLit(false) + val s1 = st.e1 + val s2 = st.e2 + val s3 = st.e3 + val args1 = s1.xs ++ s1.ys + val args2 = s2.xs ++ s2.ys + val args3 = s3.xs ++ s3.ys + val f1 = s1.e + val f2 = s2.e + val f3 = s3.e + val assertExpr = E.and(E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))), intersection) + val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val ufn1 = _apply(ufMap(s1)) + val ufn2 = _apply(ufMap(s2)) + val ufn3 = _apply(ufMap(s3)) + val assumeExpr = E.forall(st.e1.ys, E.eq(ufn1, E.mul(ufn2, ufn3))) + val assumeStmt = l.AssumeStmt(assumeExpr, None) + List(assertStmt, assumeStmt) + } + def rewriteIndLb(ufMap : UFMap, indlb : IndLbStmt) : List[l.Statement] = { // First we want f(x, n) && g(y, n) ==> f(skolem(x, y), n + 1) val f = indlb.f @@ -312,6 +336,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteUb(ufmap, ub) case andUb : AndUbStmt => rewriteAndUb(ufmap, andUb) + case disj : DisjointStmt => + rewriteDisjoint(ufmap, disj) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-disjoint.ucl b/test/modelcounter/test-disjoint.ucl new file mode 100644 index 000000000..5df6c3990 --- /dev/null +++ b/test/modelcounter/test-disjoint.ucl @@ -0,0 +1,11 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 5 <= n < 10; + define h(n : integer, m : integer) : boolean = (1 <= n < 10) && (5 <= m < 10); + + proof { + assert disjoint: #[(n: integer, m : integer)] :: (h(n, m)) == + #[(n: integer)] :: (f(n)) * + #[(m: integer)] :: (g(m)); + } +} \ No newline at end of file From 8079384b4f35459e58b9cf9800b2b56583f60760 Mon Sep 17 00:00:00 2001 From: ssahai Date: Thu, 14 May 2020 13:03:36 +0530 Subject: [PATCH 093/119] added injectivity rule --- .../extensions/modelcounts/UMCLanguage.scala | 22 +++++++++- .../extensions/modelcounts/UMCParser.scala | 8 +++- .../extensions/modelcounts/UMCRewriter.scala | 44 +++++++++++++++++++ test/modelcounter/test-injectivity.ucl | 8 ++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 test/modelcounter/test-injectivity.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 0ee4aecd2..5cad4dfed 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -244,7 +244,7 @@ case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : val e_fp = rewriter(fp.e) val e_f = rewriter(f.e) val e_g = rewriter(g.e) - val skolemsP = skolems.map(rewriter(_)).flatten + val skolemsP = skolems.flatMap(rewriter(_)) (e_fp, e_f, e_g) match { case (Some(e1), Some(e2), Some(e3)) => val fpNew = CountingOp(fp.xs, fp.ys, e1) @@ -316,6 +316,26 @@ case class DisjointStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) exten } } +case class InjectivityStmt(f : CountingOp, g: CountingOp, skolems : List[l.Expr] ) extends Statement { + override val hashId = 130010 + override val md5hashCode = computeMD5Hash(f, g, skolems) + override def toLines = + List("assert injectivity: " + f.toString() + " <= " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + override val countingOps = Seq(f, g) + override val expressions = Seq(f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val skolemsP = skolems.flatMap(rewriter(_)) + (rewriter(f.e), rewriter(g.e)) match { + case (Some(e1p), Some(e2p)) => + val e1New = CountingOp(f.xs, f.ys, e1p) + val e2New = CountingOp(g.xs, g.ys, e2p) + Some(InjectivityStmt(e1New, e2New, skolemsP)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index ef9ac50d6..a7e889d67 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -55,11 +55,12 @@ class UMCParser extends l.UclidParser { lazy val KwAndUB = "andUB" lazy val KwDisjoint = "disjoint" lazy val KwOr = "or" + lazy val KwInj = "injectivity" lazy val KwIndLb = "indLB" lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint) + KwDisjoint, KwInj) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -153,6 +154,11 @@ class UMCParser extends l.UclidParser { (CountingExpr <~ "==") ~ (CountingExpr <~ "*") ~ CountingExpr <~ ";" ^^ { case e1 ~ e2 ~ e3 => DisjointStmt(e1, e2, e3) } | + KwAssert ~ KwInj ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ es => { + InjectivityStmt(e1, e2, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 20c962e31..97e703dd3 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -230,6 +230,48 @@ class UMCRewriter(cntProof : CountingProof) { List(assertStmt, assumeStmt) } + def rewriteInjectivity(ufMap : UFMap, inj : InjectivityStmt) : List[l.Statement] = { + // forall x. f(x) ==> g(skolem(x)) + val f = inj.f + val g = inj.g + val f_x = f.e + val g_y = g.e + val skSubs = (g.xs.map(_._1.asInstanceOf[l.Expr]) zip inj.skolems).toMap + val conseq = new ExprRewriter(skSubs).rewrite(g_y) + val impl = E.implies(f_x, conseq) + val qVars = f.xs ++ f.ys + val qOp = E.forall(qVars, impl) + val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + + // Next we want to show injectivity of the skolem: + // f(x1) && f(x2) && (x1 != x2) ==> skolem(x1) != skolem(x2) + val x1s = f.xs.map(p => generateId(p._1.toString())) + val x2s = f.xs.map(p => generateId(p._1.toString())) + val rwx1 = new ExprRewriter((f.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((f.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + + val f_x1n = rwx1.rewrite(f.e) + val f_x2n = rwx2.rewrite(f.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + val sk1s = inj.skolems.map(sk => rwx1.rewrite(sk)) + val sk2s = inj.skolems.map(sk => rwx2.rewrite(sk)) + val ante2 = E.andL(List(f_x1n, f_x2n, xdiff)) + val skdiff = E.orL((sk1s zip sk2s).map(p => E.distinct(p._1, p._2))) + val impl2 = E.implies(ante2, skdiff) + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + + // Now the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val leqExpr = E.le(ufn, ugn) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, leqExpr), None) + + List(liftAssertStmt, injAssertStmt, assumpStmt1) + } + + def rewriteDisjoint(ufMap : UFMap, st : DisjointStmt) : List[l.Statement] = { val e2s = st.e2.xs.toSet val e3s = st.e3.xs.toSet @@ -338,6 +380,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteAndUb(ufmap, andUb) case disj : DisjointStmt => rewriteDisjoint(ufmap, disj) + case inj : InjectivityStmt => + rewriteInjectivity(ufmap, inj) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-injectivity.ucl b/test/modelcounter/test-injectivity.ucl new file mode 100644 index 000000000..349f09ae4 --- /dev/null +++ b/test/modelcounter/test-injectivity.ucl @@ -0,0 +1,8 @@ +module main { + define f(n : integer) : boolean = 1 <= n < 10; + define g(n : integer) : boolean = 15 <= n < 30; + + proof { + assert injectivity: #[(n: integer)] :: (f(n)) <= #[(m: integer)] :: (g(m)) skolems(n+14); + } +} \ No newline at end of file From 80dcbb6289d4f044bfac7b5102c5debd181d9cc7 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 15:31:49 +0530 Subject: [PATCH 094/119] zkhat example --- test/modelcounter/counting-zkhat.ucl | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/modelcounter/counting-zkhat.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl new file mode 100644 index 000000000..526aba2da --- /dev/null +++ b/test/modelcounter/counting-zkhat.ucl @@ -0,0 +1,46 @@ +// Model counting in ZK Hats - Motivating example in CAV 2020 paper + +module main { + + define V(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define Vf(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define V1(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: !Y[i]); + + define W(i : boolean) : boolean = 0 <= i < 2; + + proof { + // Proof rules 2 - 7 from paper. + + // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) + assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == + #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + + #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); + + // 3. (ConstEq) #Y. V1(Y, R) = 1 + assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; + + // 4. (ConstEq) #Y. Vf(Y, 1) = 2 + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + + // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) + // TODO + + // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) + // TODO : Not Working yet + + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R-1)) * + #[(i: integer)] :: (W(i)) + skolems(Y[(R+1) -> i]); + + // 7. (Range) #i. W(i) == 2 + assert range: #[(i: integer)] :: (W(i)) == 2; + + } +} From b15cd63d36b14b1416c29606bafcf774954f848c Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 16:20:55 +0530 Subject: [PATCH 095/119] parsing indUB. Rewriting is pending for now. --- .../extensions/modelcounts/UMCLanguage.scala | 32 +++++++++++++++++++ .../extensions/modelcounts/UMCParser.scala | 8 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 5cad4dfed..a443208a9 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -336,6 +336,38 @@ case class InjectivityStmt(f : CountingOp, g: CountingOp, skolems : List[l.Expr] } } +case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys(0)._2.isInt) + assert (fp.ys == f.ys && f.ys == g.ys) + val n = fp.ys(0)._1 + assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) + + override val hashId = 130011 + override val md5hashCode = computeMD5Hash(fp, f, g, skolems) + override def toLines = { + List("assert indUB: " + fp.toString + " <= " + + f.toString() + " * " + g.toString() + " skolems (" + + Utils.join(skolems.map(_.toString()), ", ") + ");") + } + override val countingOps = Seq(fp, f, g) + override val expressions = Seq(fp, f, g) ++ skolems + override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { + val e_fp = rewriter(fp.e) + val e_f = rewriter(f.e) + val e_g = rewriter(g.e) + val skolemsP = skolems.flatMap(rewriter(_)) + (e_fp, e_f, e_g) match { + case (Some(e1), Some(e2), Some(e3)) => + val fpNew = CountingOp(fp.xs, fp.ys, e1) + val fNew = CountingOp(f.xs, f.ys, e2) + val gNew = CountingOp(g.xs, g.ys, e3) + Some(IndUbStmt(fpNew, fNew, gNew, skolemsP)) + case _ => None + } + } +} + case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index a7e889d67..877076055 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -57,10 +57,11 @@ class UMCParser extends l.UclidParser { lazy val KwOr = "or" lazy val KwInj = "injectivity" lazy val KwIndLb = "indLB" + lazy val KwIndUb = "indUB" lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint, KwInj) + KwDisjoint, KwInj, KwIndUb) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -159,6 +160,11 @@ class UMCParser extends l.UclidParser { InjectivityStmt(e1, e2, es) } } | + KwAssert ~ KwIndUb ~ ":" ~> CountingExpr ~ ("<=" ~> CountingExpr) ~ ("*" ~> CountingExpr) ~ (KwSkolems ~> ExprList <~ ";") ^^ { + case e1 ~ e2 ~ e3 ~ es => { + IndUbStmt(e1, e2, e3, es) + } + } | KwAssert ~> CExpr <~ ";" ^^ { case e => AssertStmt(e) } From 2f19e331e5a9e3e5228f673bf7febaa9a1aa6afb Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 19:16:38 +0530 Subject: [PATCH 096/119] added support for lemmas via lemma block - parsing successfully --- .../extensions/modelcounts/UMCLanguage.scala | 19 ++++--- .../extensions/modelcounts/UMCParser.scala | 56 +++++++++++++++++-- .../extensions/modelcounts/UMCRewriter.scala | 3 +- test/modelcounter/test-lemma.ucl | 20 +++++++ 4 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 test/modelcounter/test-lemma.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index a443208a9..f0403fc4c 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -368,10 +368,12 @@ case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } -case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[Statement]) extends l.ASTNode { +case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.Decl], + stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + - Utils.join(decls.map(" " + _.toString()), "\n") + + Utils.join(decls.map(" " + _.toString()), "\n") + "\n\n" + + Utils.join(lemmas.map(" " + _.toString()), "\n") + "\n\n proof {\n" + Utils.join(stmts.map(st => " " + st.toString()), "\n") + "\n }\n}" @@ -381,8 +383,9 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S def rewriterFn(expr : l.Expr) : Option[l.Expr] = { rewriter.visitExpr(expr, ctx) } - val stmtsP = stmts.map(st => st.rewrite(rewriterFn)).flatten - CountingProof(id, decls, stmtsP) + + val stmtsP = stmts.flatMap(st => st.rewrite(rewriterFn)) + CountingProof(id, decls, lemmas, stmtsP) } override val hashId = 131001 override val md5hashCode = computeMD5Hash(id, decls, stmts) @@ -392,7 +395,7 @@ case class CountingProof(id : l.Identifier, decls : List[l.Decl], stmts : List[S object UMCExpressions { // Helper functions to more easily construct expressions. def forall(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - if (vs.size > 0) { + if (vs.nonEmpty) { val op = l.ForallOp(vs, List.empty) l.OperatorApplication(op, List(e)) } else { @@ -401,7 +404,7 @@ object UMCExpressions { } def exists(vs : List[(l.Identifier, l.Type)], e : l.Expr) = { - assert (vs.size > 0) + assert (vs.nonEmpty) val op = l.ExistsOp(vs, List.empty) l.OperatorApplication(op, List(e)) } @@ -411,7 +414,7 @@ object UMCExpressions { } def andL(es : Seq[l.Expr]) = { - assert (es.size >= 1) + assert (es.nonEmpty) es.foldLeft(l.BoolLit(true).asInstanceOf[l.Expr])((acc, e) => and(acc, e)) } @@ -420,7 +423,7 @@ object UMCExpressions { } def orL(es : Seq[l.Expr]) = { - assert (es.size >= 1) + assert (es.nonEmpty) es.foldLeft(l.BoolLit(false).asInstanceOf[l.Expr])((acc, e) => or(acc, e)) } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 877076055..630661146 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -39,14 +39,14 @@ package uclid.extensions.modelcounts -import uclid.{lang => l} -import uclid.Utils -import uclid.lang.Identifier -import uclid.lang.Type +import uclid.{Utils, lang => l} +import uclid.lang.{Decl, Identifier, ProcedureAnnotations, Type} import uclid.smt.IntLit import uclid.extensions.modelcounts.{UMCExpressions => E} class UMCParser extends l.UclidParser { + lazy val KwLemmas = "lemmas" + lazy val KwLemma = "lemma" lazy val KwProof = "proof" lazy val KwConstLB = "constLB" lazy val KwConstUB = "constUB" @@ -61,7 +61,7 @@ class UMCParser extends l.UclidParser { lazy val KwSkolems = "skolems" lexical.reserved += (KwProof, KwOr, KwConstLB, KwConstUB, KwConstEq, KwIndLb, KwSkolems, KwUB, KwAndUB, - KwDisjoint, KwInj, KwIndUb) + KwDisjoint, KwInj, KwIndUb, KwLemmas, KwLemma) lazy val UMCDecl: PackratParser[l.Decl] = positioned (TypeDecl | DefineDecl | FuncDecl | AxiomDecl) @@ -169,15 +169,59 @@ class UMCParser extends l.UclidParser { case e => AssertStmt(e) } } + + lazy val LemmaDecl : PackratParser[l.ProcedureDecl] = positioned { + KwLemma ~> ProcedureAnnotationList.? ~ Id ~ IdTypeList ~ (KwReturns ~> IdTypeList) ~ + rep(ProcedureVerifExpr) ~ BlkStmt ^^ + { case annotOpt ~ id ~ args ~ outs ~ verifExprs ~ body => + val annotations = annotOpt match { + case Some(ids) => ProcedureAnnotations(ids.toSet) + case None => ProcedureAnnotations(Set.empty) + } + val verifExprList = verifExprs.flatMap(v => v) + val requiresList = collectRequires(verifExprList) + val ensuresList = collectEnsures(verifExprList) + val modifiesList = collectModifies(verifExprList) + l.ProcedureDecl(id, l.ProcedureSig(args,outs), + body, requiresList, ensuresList, modifiesList.toSet, annotations) } | + // procedure with no return value + KwLemma ~> ProcedureAnnotationList.? ~ Id ~ IdTypeList ~ rep(ProcedureVerifExpr) ~ BlkStmt ^^ + { case annotOpt ~ id ~ args ~ verifExprs ~ body => + val annotations = annotOpt match { + case Some(ids) => ProcedureAnnotations(ids.toSet) + case None => ProcedureAnnotations(Set.empty) + } + val verifExprList = verifExprs.flatMap(v => v) + val requiresList = collectRequires(verifExprList) + val ensuresList = collectEnsures(verifExprList) + val modifiesList = collectModifies(verifExprList) + l.ProcedureDecl(id, l.ProcedureSig(args, List.empty), + body, requiresList, ensuresList, modifiesList.toSet, annotations) } + } + + lazy val LemmaDeclarations: PackratParser[l.ProcedureDecl] = + positioned ( LemmaDecl ); + + lazy val LemmaBlock: PackratParser[List[Decl]] = { + KwLemmas ~ "{" ~> rep(LemmaDeclarations) <~ "}" + } + lazy val ProofStmt: PackratParser[Statement] = positioned ( Stmt ); + lazy val ProofScript: PackratParser[List[Statement]] = { KwProof ~ "{" ~> rep(ProofStmt) <~ "}" } + lazy val CntProof: PackratParser[CountingProof] = positioned { KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ (ProofScript <~ "}") ^^ { case id ~ decls ~ proof => { - CountingProof(id, decls, proof) + CountingProof(id, decls, List(), proof) + } + } | + KwModule ~> Id ~ ("{" ~> rep(UMCDecl)) ~ LemmaBlock ~ (ProofScript <~ "}") ^^ { + case id ~ decls ~ lemmas ~ proof => { + CountingProof(id, decls, lemmas, proof) } } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 97e703dd3..560a9624f 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -415,8 +415,9 @@ class UMCRewriter(cntProof : CountingProof) { l.BlockStmt(List.empty, newProofStmts), List.empty, List.empty, Set.empty, l.ProcedureAnnotations(Set.empty)) val prevDecls = cntProof.decls.filter(p => !p.isInstanceOf[l.ProcedureDecl]) + val prevProcDecls = cntProof.lemmas val moduleP = l.Module(cntProof.id, - prevDecls ++ ufDecls ++ List(newProofProc), + prevDecls ++ prevProcDecls ++ ufDecls ++ List(newProofProc), controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl new file mode 100644 index 000000000..1706d62a0 --- /dev/null +++ b/test/modelcounter/test-lemma.ucl @@ -0,0 +1,20 @@ +module main { + define f (n: integer): boolean = 1 <= n < 10; + + lemmas{ + lemma lemma1() + returns (b: boolean) + { + b= true; + } + lemma lemma2() + returns (i: integer) + { + i= 42; + } + } + + proof { + assert range: #[(n: integer)] :: (f(n)) == 9; + } +} \ No newline at end of file From 6ad096a94e66e3a0929f4328bf89afd8d679b860 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 20:24:14 +0530 Subject: [PATCH 097/119] verifying the lemmas in the control block --- .../scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- .../scala/uclid/extensions/modelcounts/UMCParser.scala | 2 +- .../scala/uclid/extensions/modelcounts/UMCRewriter.scala | 7 ++++++- test/modelcounter/test-lemma.ucl | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index f0403fc4c..820000d0d 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -368,7 +368,7 @@ case class IndUbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : } } -case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.Decl], +case class CountingProof(id : l.Identifier, decls : List[l.Decl], lemmas: List[l.ProcedureDecl], stmts : List[Statement]) extends l.ASTNode { override def toString = { "module " + id.toString() + " {\n" + diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 630661146..8c0a22c4f 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -202,7 +202,7 @@ class UMCParser extends l.UclidParser { lazy val LemmaDeclarations: PackratParser[l.ProcedureDecl] = positioned ( LemmaDecl ); - lazy val LemmaBlock: PackratParser[List[Decl]] = { + lazy val LemmaBlock: PackratParser[List[l.ProcedureDecl]] = { KwLemmas ~ "{" ~> rep(LemmaDeclarations) <~ "}" } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 560a9624f..95e9a7706 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -403,6 +403,11 @@ class UMCRewriter(cntProof : CountingProof) { None, None), ) + lazy val verifyLemmas : List[l.GenericProofCommand] = + cntProof.lemmas.map(lem => l.GenericProofCommand( + l.Identifier("verify"), + List.empty, List((lem.id, lem.id.toString+ " in Lemma block.")), + None, None)) def process() : l.Module = { val countingOps = identifyCountOps(cntProof.stmts) @@ -418,7 +423,7 @@ class UMCRewriter(cntProof : CountingProof) { val prevProcDecls = cntProof.lemmas val moduleP = l.Module(cntProof.id, prevDecls ++ prevProcDecls ++ ufDecls ++ List(newProofProc), - controlBlock, List.empty) + verifyLemmas ++ controlBlock, List.empty) println(ufMap.toString()) println(ufDecls.toString()) moduleP diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl index 1706d62a0..8dbfe32b7 100644 --- a/test/modelcounter/test-lemma.ucl +++ b/test/modelcounter/test-lemma.ucl @@ -9,6 +9,7 @@ module main { } lemma lemma2() returns (i: integer) + ensures i > 10; { i= 42; } From 13466edb15ab3bbaa0c78e5853c3e8b188f87bc9 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 15 May 2020 20:51:06 +0530 Subject: [PATCH 098/119] indLB for zk-hat working now --- test/modelcounter/counting-zkhat.ucl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl index 526aba2da..8019cea75 100644 --- a/test/modelcounter/counting-zkhat.ucl +++ b/test/modelcounter/counting-zkhat.ucl @@ -12,7 +12,7 @@ module main { define V1(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (forall (i : integer) :: !Y[i]); - define W(i : boolean) : boolean = 0 <= i < 2; + define W(i : integer) : boolean = 0 <= i < 2; proof { // Proof rules 2 - 7 from paper. @@ -32,12 +32,10 @@ module main { // TODO // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - // TODO : Not Working yet - - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) >= - #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R-1)) * - #[(i: integer)] :: (W(i)) - skolems(Y[(R+1) -> i]); + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (R: integer)] :: (W(i)) + skolems(Y[(R) -> (i == 1)]); // 7. (Range) #i. W(i) == 2 assert range: #[(i: integer)] :: (W(i)) == 2; From d5d1d02794372a468740fc250cfac9522338dbee Mon Sep 17 00:00:00 2001 From: Pramod Subramanyan Date: Sat, 16 May 2020 23:30:14 +0530 Subject: [PATCH 099/119] minor edits while playing around with examples. --- test/modelcounter/counting-zkhat.ucl | 12 ++--- test/modelcounter/debug2.ucl | 65 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 test/modelcounter/debug2.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl index 8019cea75..2d14c0701 100644 --- a/test/modelcounter/counting-zkhat.ucl +++ b/test/modelcounter/counting-zkhat.ucl @@ -4,10 +4,10 @@ module main { define V(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) - && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); define Vf(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); define V1(Y : [integer]boolean, R : integer) : boolean = (R >= 0) && (forall (i : integer) :: !Y[i]); @@ -26,19 +26,19 @@ module main { assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; // 4. (ConstEq) #Y. Vf(Y, 1) = 2 - assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) - // TODO + // TODO // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * #[(i: integer) for (R: integer)] :: (W(i)) skolems(Y[(R) -> (i == 1)]); // 7. (Range) #i. W(i) == 2 - assert range: #[(i: integer)] :: (W(i)) == 2; + assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; } } diff --git a/test/modelcounter/debug2.ucl b/test/modelcounter/debug2.ucl new file mode 100644 index 000000000..7650c33ad --- /dev/null +++ b/test/modelcounter/debug2.ucl @@ -0,0 +1,65 @@ + module main { + define V (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))); + define Vf (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))); + define V1 (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: !((Y)[i]))); + define W (i: integer): boolean = ((0 <= i) && (i < 2)); + function count_1(R: integer): integer; // line 0 + axiom assump_1 : (forall (R : integer) :: (count_1(R) >= 0)); // + function count_2(R: integer): integer; // line 0 + axiom assump_2 : (forall (R : integer) :: (count_2(R) >= 0)); // + function count_3(R: integer): integer; // line 0 + axiom assump_3 : (forall (R : integer) :: (count_3(R) >= 0)); // + function count_4(R: integer): integer; // line 0 + axiom assump_4 : (forall (R : integer) :: (count_4(R) >= 0)); // + function count_5(R: integer): integer; // line 0 + axiom assump_5 : (forall (R : integer) :: (count_5(R) >= 0)); // + procedure countingProof() returns () + { + assert ((forall (Y : [integer]boolean, R : integer) :: (((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) <==> (((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) || ((R >= 0) && (forall (i : integer) :: !((Y)[i])))))) && (forall (Y : [integer]boolean, R : integer) :: !((((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) && ((R >= 0) && (forall (i : integer) :: !((Y)[i]))))))); // line 0 + assume (forall (R : integer) :: (count_4(R) == (count_1(R) + count_5(R)))); // line 0 + { + var Y_1 : [integer]boolean; // line 26 + var R : integer; // line 26 + assert [cover, SATOnly]: (forall (R : integer) :: (R >= 0 ==> (R >= 0 && (forall (i : integer) :: !Y_1[i])))); // line 0 + } + assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) >= 1))); // line 0 + { + var Y_2 : [integer]boolean; // line 26 + var Y_3 : [integer]boolean; // line 26 + var R : integer; // line 26 + assert [cover]: (forall (R : integer) :: ((R >= 0) ==> (((true && ((R >= 0) && (forall (i : integer) :: !((Y_2)[i])))) && ((R >= 0) && (forall (i : integer) :: !((Y_3)[i])))) && distinct({Y_2}, {Y_3})))); // line 0 + } + assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) < 2))); // line 0 + { + var Y_4 : [integer]boolean; // line 29 + var Y_5 : [integer]boolean; // line 29 + var R : integer; // line 29 + assert [cover, SATOnly]: (forall (R : integer) :: ((R == 1) ==> (((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_4)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_5)[i]))))) && distinct({Y_4}, {Y_5})))); // line 0 + } + assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) >= 2))); // line 0 + { + var Y_6 : [integer]boolean; // line 29 + var Y_7 : [integer]boolean; // line 29 + var Y_8 : [integer]boolean; // line 29 + var R : integer; // line 29 + assert [cover]: (forall (R : integer) :: ((R == 1) ==> ((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_6)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_7)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_8)[i]))))) && distinct({Y_6}, {Y_7}, {Y_8})))); // line 0 + } + assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) < 3))); // line 0 + assert (forall (Y : [integer]boolean, i : integer, R : integer) :: ((((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) && ((0 <= i) && (i < 2))) ==> (((R + 1) >= 0) && (forall (i : integer) :: (((i < 0) || (i >= (R + 1))) ==> !(((Y)[R -> (i == 1)])[i])))))); // line 0 + assert (forall (Y_9 : [integer]boolean, Y_10 : [integer]boolean, i_1 : integer, i_2 : integer, R : integer) :: ((((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_9)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_10)[i]))))) && ((0 <= i_1) && (i_1 < 2))) && ((0 <= i_2) && (i_2 < 2))) && ((false || distinct(Y_9, Y_10)) || (false || distinct(i_1, i_2)))) ==> (false || distinct((Y_9)[R -> (i_1 == 1)], (Y_10)[R -> (i_2 == 1)])))); // line 0 + assume (forall (R : integer) :: (count_4((R + 1)) >= (count_4(R) * count_3(R)))); // line 0 + assume (forall (R : integer) :: (count_4((R + 1)) == count_2(R))); // line 0 + assume (forall (R : integer) :: (count_3(R) == (if(0 < 2) then 2 else 0))); // line 0 + assert (forall (R : integer) :: (count_3(R) == 2)); // line 0 + + assert (forall (R : integer) :: count_4(R+1) >= count_4(R) * 2); + assert (count_4(1) >= 2); + assert (forall (R : integer) :: (R >= 0) ==> (count_1(R) == count_4(R) - 1)); + } + control { + v = verify(countingProof /* countingProof*/); // line 0 + check; // line 0 + print_results; // line 0 + } + } + From 5829b6e78b27f3a42935a77346fd3c7e77c1401a Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 09:19:31 +0530 Subject: [PATCH 100/119] updated vim syntax file --- vim/uclid.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vim/uclid.vim b/vim/uclid.vim index 5c2977dae..d9827be78 100644 --- a/vim/uclid.vim +++ b/vim/uclid.vim @@ -21,6 +21,7 @@ syn keyword ucl4Decl module init next control function procedure retu syn keyword ucl4Cmd unroll lazysc check print_module print_cex print_results k_induction_base k_induction_step induction clear_context synthesize_invariant set_solver_option " user labels syn keyword ucl4Constant false true +syn keyword ucl4Count proof range constUB constLB constEq UB andUB disjoint or injectivity indLB indUB skolems lemmas lemma syn match ucl4Identifier display "[A-Za-z_][A-Za-z0-9_]*" @@ -63,5 +64,6 @@ hi def link ucl4Cmd Define hi def link ucl4Operator Special hi def link ucl4Delimiter Normal hi def link ucl4Number Number +hi def link ucl4Count Keyword let b:current_syntax = "ucl" From cbd524132ed4814a5c3a856cb32a8028e5492f4d Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 09:22:13 +0530 Subject: [PATCH 101/119] minor edit to syntax file --- vim/uclid.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim/uclid.vim b/vim/uclid.vim index d9827be78..c16efd22b 100644 --- a/vim/uclid.vim +++ b/vim/uclid.vim @@ -42,7 +42,7 @@ syn match ucl4Number "\<\d\+bv\d\+\>" syn match ucl4Number "\<0[xX][0-9A-F]\+bv\d\+\>" syn match ucl4Number "\<0[bB][01]\+bv\d\+\>" syn match ucl4Delimiter "\[\|\]\|(\|)" -syn match ucl4Operator "=\|==\|+\|-\|*\|&&\|||\|^\|!\|==>\|<==>\|<\|<=\|>\|>=" +syn match ucl4Operator "=\|==\|+\|-\|*\|&&\|||\|^\|!\|==>\|<==>\|<\|<=\|>\|>=\|#" syn match ucl4UComparator "<_u\|<=_u\|>_u\|>=_u" syn region ucl4MultilineComment start="/\*" end="\*/" From e18f5a43db9f888a66b9ecd814cdae8a684f0c22 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 10:56:18 +0530 Subject: [PATCH 102/119] added assumptions to range statement to cater symbolic bounds --- .../uclid/extensions/modelcounts/UMCLanguage.scala | 10 +++++----- .../scala/uclid/extensions/modelcounts/UMCParser.scala | 7 ++++++- .../uclid/extensions/modelcounts/UMCRewriter.scala | 2 +- test/modelcounter/test-range-symb.ucl | 8 ++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 test/modelcounter/test-range-symb.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 820000d0d..ea75bddf7 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -114,7 +114,7 @@ case class OrStmt(e1 : CountingOp, e2 : CountingOp, e3 : CountingOp) extends Sta } } -case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { +case class RangeStmt(op : CountingOp, cnt : l.Expr, assump: l.Expr) extends Statement { lazy val lb : l.Expr = { op.e match { case l.OperatorApplication(l.ConjunctionOp(), @@ -146,14 +146,14 @@ case class RangeStmt(op : CountingOp, cnt : l.Expr) extends Statement { } override val hashId = 130002 override val md5hashCode = computeMD5Hash(op, cnt) - override def toLines = List("assert range: " + op.toString() + " == " + cnt.toString()) + override def toLines = List("assert range: " + assump.toString + " ==> " + op.toString() + " == " + cnt.toString()) override val countingOps = Seq(op) override val expressions = Seq(op, cnt) override def rewrite(rewriter : l.Expr => Option[l.Expr]) : Option[Statement] = { - (rewriter(op.e), rewriter(cnt)) match { - case (Some(ep), Some(cntp)) => + (rewriter(op.e), rewriter(cnt), rewriter(assump)) match { + case (Some(ep), Some(cntp), Some(aP)) => val op1 = CountingOp(op.xs, op.ys, ep) - Some(RangeStmt(op1, cntp)) + Some(RangeStmt(op1, cntp, aP)) case _ => None } } diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 8c0a22c4f..1f80cd9e8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -104,7 +104,12 @@ class UMCParser extends l.UclidParser { } | KwAssert ~ KwRange ~ ":" ~> CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { case e1 ~ e2 => { - RangeStmt(e1, e2) + RangeStmt(e1, e2, l.BoolLit(true)) + } + } | + KwAssert ~ KwRange ~ ":" ~> (Expr <~ "==>") ~ CountingExpr ~ ("==" ~> Expr) <~ ";" ^^ { + case assump ~ e1 ~ e2 => { + RangeStmt(e1, e2, assump) } } | KwAssert ~ KwConstLB ~ ":" ~> CountingExpr ~ (">=" ~> Integer) <~ ";" ^^ { diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 95e9a7706..2cadcaef0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -154,7 +154,7 @@ class UMCRewriter(cntProof : CountingProof) { val ufn = _apply(ufMap(st.op)) val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) - val assertExpr = E.forall(st.op.ys, E.eq(ufn, st.cnt)) + val assertExpr = E.forall(st.op.ys, E.implies(st.assump, E.eq(ufn, st.cnt))) val assertStmt = l.AssertStmt(assertExpr, None, List.empty) List(assumeStmt, assertStmt) } diff --git a/test/modelcounter/test-range-symb.ucl b/test/modelcounter/test-range-symb.ucl new file mode 100644 index 000000000..a5a27398b --- /dev/null +++ b/test/modelcounter/test-range-symb.ucl @@ -0,0 +1,8 @@ +module main { + + define f(i : integer, j: integer, R: integer) : boolean = j <= i < R; + + proof { + assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (f(i, j, R)) == (R - j); + } +} From d40d5c46fc6ffa615a2a32d9cc463ce11a361051 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 12:54:23 +0530 Subject: [PATCH 103/119] attempting Array shuffle example - constEQ going UNDEF --- .../extensions/modelcounts/UMCLanguage.scala | 4 +- .../extensions/modelcounts/UMCRewriter.scala | 6 +-- test/modelcounter/counting-array-shuffle.ucl | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/modelcounter/counting-array-shuffle.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index ea75bddf7..72f8fad55 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -225,9 +225,9 @@ case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends St } case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { - assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size == 1) + assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size >= 1) assert (fp.ys(0)._2.isInt) - assert (fp.ys == f.ys && f.ys == g.ys) + assert (fp.ys == f.ys) val n = fp.ys(0)._1 assert (new ExprRewriter(Map(n -> UMCExpressions.plus(n, l.IntLit(1)))).rewrite(f.e) == fp.e) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 2cadcaef0..5cf2eabfe 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -308,7 +308,7 @@ class UMCRewriter(cntProof : CountingProof) { (indlb.n.asInstanceOf[l.Expr] -> nplus1) val conseq = new ExprRewriter(skSubs).rewrite(f_xn) val impl = E.implies(ante, conseq) - val qVars = f.xs ++ g.xs ++ f.ys + val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys val qOp = E.forall(qVars, impl) val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) @@ -339,7 +339,7 @@ class UMCRewriter(cntProof : CountingProof) { val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ - (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys + (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) // Finally, we have to produce the assumption. @@ -347,7 +347,7 @@ class UMCRewriter(cntProof : CountingProof) { val ugn = _apply(ufMap(g)) val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) - val assumpStmt1 = l.AssumeStmt(E.forall(f.ys, geqExpr), None) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl new file mode 100644 index 000000000..4c7d8e042 --- /dev/null +++ b/test/modelcounter/counting-array-shuffle.ucl @@ -0,0 +1,38 @@ +// Model counting in Array Shuffle + +module main { + + define V(Y : [integer]integer, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: 0 <= i < R ==> i <= Y[i] <= R) + && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); + + define Vf(Y : [integer]integer, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> (Y[i] == 0)); + + define W(i : integer, j: integer, R: integer) : boolean = j <= i < R; + + + proof { + + // (ConstEq) #Y. V(Y, 1) = 1 + // assert constEq: R == 1 ==> #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) == 1; + + // Proving for Vf (NOT REQUIRED) + // (IndLB) #Y. Vf(Y, R) >= #Y. Vf(Y, R-1) * #i. W(i, j, R) + assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) + skolems(Y[(R) -> i]); + + // Proving for V + // (IndLB) #Y. V(Y, R) >= #Y. V(Y, R-1) * #i. W(i, j, R) + assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R + 1)) >= + #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) * + #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) + skolems(Y[(R) -> i]); + + // (Range) #i. W(i, j, R) == R - j + assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) == (R - j); + } +} + From 2d7a5245885989fb3d9dc69962e938ce354d58c1 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 15:56:40 +0530 Subject: [PATCH 104/119] fixed ite printing --- src/main/scala/uclid/lang/UclidLanguage.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 08ea601a9..413c3d5fe 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -746,6 +746,8 @@ case class OperatorApplication(op: Operator, operands: List[Expr]) extends Possi operands(0).toString + "." + i.toString case SelectFromInstance(f) => operands(0).toString + "." + f.toString + case ITEOp() => + "(if (" + operands(0).toString + ") then (" + operands(1).toString + ") else (" + operands(2).toString + "))" case ForallOp(_, _) | ExistsOp(_, _) => "(" + op.toString + operands(0).toString + ")" case _ => From d493f5b49477f240cff4d36f42fd0020b4a7d4ad Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 16:49:23 +0530 Subject: [PATCH 105/119] fixed satonly decorator and assert printing --- src/main/scala/uclid/lang/UclidLanguage.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 413c3d5fe..44aa3c7d3 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -849,7 +849,7 @@ case object CoverDecorator extends ExprDecorator { override val md5hashCode = computeMD5Hash } case object SATOnlyDecorator extends ExprDecorator { - override def toString = "satonly" + override def toString = "SATOnly" override val hashId = 2606 override val md5hashCode = computeMD5Hash } @@ -1160,7 +1160,7 @@ case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDec override def toLines = { val name = "assert" val decoratorStr = if (decorators.size > 0) { - " [" + Utils.join(decorators.map(_.toString()), ", ") + "] " + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] :" } else { "" } val prefix = id match { case Some(n) => name + " " + n.toString() + decoratorStr + ": " From 503799371d85f58b898d0b4b3513dc1d58f873c9 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 20:54:13 +0530 Subject: [PATCH 106/119] UFs can take more than one arguements now in IndLB --- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 5cf2eabfe..03097a0bb 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -345,14 +345,12 @@ class UMCRewriter(cntProof : CountingProof) { // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) val ugn = _apply(ufMap(g)) - val ufnplus1 = E.apply(ufMap(f).id, List(nplus1)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1) ++ ufMap(f).sig.args.drop(1).map(_._1)) val geqExpr = E.ge(ufnplus1, E.mul(ufn, ugn)) val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) - val ufpn = _apply(ufMap(indlb.fp)) val eqExpr = E.eq(ufnplus1, ufpn) val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) - List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } From 049edb171c0dab1842bf51f3539837e6ac07c530 Mon Sep 17 00:00:00 2001 From: ssahai Date: Sun, 17 May 2020 20:56:20 +0530 Subject: [PATCH 107/119] indLB can take more than one ys values --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 72f8fad55..07d54895a 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -225,7 +225,7 @@ case class ConstEqStmt(e : CountingOp, v : l.IntLit, assump : l.Expr) extends St } case class IndLbStmt(fp : CountingOp, f : CountingOp, g : CountingOp, skolems : List[l.Expr]) extends Statement { - assert (fp.ys.size == 1 && f.ys.size == 1 && g.ys.size >= 1) + assert (fp.ys.size >= 1 && f.ys.size >= 1 && g.ys.size >= 1) assert (fp.ys(0)._2.isInt) assert (fp.ys == f.ys) val n = fp.ys(0)._1 From 9e0d1f8f98585a7eaf4ccbed7179d73d40e509bc Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 11:51:34 +0530 Subject: [PATCH 108/119] asserts can be named --- src/main/scala/uclid/lang/UclidParser.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/scala/uclid/lang/UclidParser.scala b/src/main/scala/uclid/lang/UclidParser.scala index ca1f33574..808e836c0 100644 --- a/src/main/scala/uclid/lang/UclidParser.scala +++ b/src/main/scala/uclid/lang/UclidParser.scala @@ -464,6 +464,13 @@ class UclidParser extends UclidTokenParsers with PackratParsers { case ids ~ e => AssertStmt(e, None, ids.map(ExprDecorator.parse(_))) } | + KwAssert ~> (Id <~ ":") ~ Expr <~ ";" ^^ { + case id ~ e => AssertStmt(e, Some(id), List.empty) + } | + KwAssert ~> (Id) ~ ("[" ~> IdList <~ "]" ~ ":") ~ Expr <~ ";" ^^ { + case id ~ ids ~ e => + AssertStmt(e, Some(id), ids.map(ExprDecorator.parse(_))) + } | KwAssume ~> Expr <~ ";" ^^ { case e => AssumeStmt(e, None) } | KwHavoc ~> Id <~ ";" ^^ { case id => HavocStmt(HavocableId(id)) } | Lhs ~ rep("," ~> Lhs) ~ "=" ~ Expr ~ rep("," ~> Expr) <~ ";" ^^ From 8bb817f4de13745519a391cab4c6e50a3baf2f1b Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 12:16:48 +0530 Subject: [PATCH 109/119] fixing printing of 2 : in case asserts have names --- src/main/scala/uclid/lang/UclidLanguage.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/lang/UclidLanguage.scala b/src/main/scala/uclid/lang/UclidLanguage.scala index 44aa3c7d3..8647b871b 100644 --- a/src/main/scala/uclid/lang/UclidLanguage.scala +++ b/src/main/scala/uclid/lang/UclidLanguage.scala @@ -1160,11 +1160,17 @@ case class AssertStmt(e: Expr, id : Option[Identifier], decorators: List[ExprDec override def toLines = { val name = "assert" val decoratorStr = if (decorators.size > 0) { - " [" + Utils.join(decorators.map(_.toString()), ", ") + "] :" - } else { "" } + " [" + Utils.join(decorators.map(_.toString()), ", ") + "] : " + } else { " " } val prefix = id match { - case Some(n) => name + " " + n.toString() + decoratorStr + ": " - case None => name + " " + decoratorStr + case Some(n) => + if (decorators.nonEmpty) { + name + " " + n.toString() + decoratorStr + } + else { + name + " " + n.toString() + " : " + } + case None => name + decoratorStr } List(prefix + e.toString() + "; // " + position.toString) } From 245a5902fc1c92d51c5779c9425ecaeb4cd9207a Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 12:27:22 +0530 Subject: [PATCH 110/119] added descriptive names to rewritten asserts --- .../extensions/modelcounts/UMCRewriter.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 03097a0bb..3d025192c 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -141,7 +141,7 @@ class UMCRewriter(cntProof : CountingProof) { val f3 = o3.e val assertExpr = E.and(E.forall(args, E.iff(f1, E.or(f2, f3))), E.forall(args, E.not(E.and(f2, f3)))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Or")), List.empty) val ufn1 = _apply(ufMap(o1)) val ufn2 = _apply(ufMap(o2)) val ufn3 = _apply(ufMap(o3)) @@ -155,7 +155,7 @@ class UMCRewriter(cntProof : CountingProof) { val assumeExpr = E.forall(st.op.ys, E.eq(ufn, E.max(l.IntLit(0), E.minus(st.ub, st.lb)))) val assumeStmt = l.AssumeStmt(assumeExpr, None) val assertExpr = E.forall(st.op.ys, E.implies(st.assump, E.eq(ufn, st.cnt))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Range")), List.empty) List(assumeStmt, assertStmt) } @@ -176,7 +176,7 @@ class UMCRewriter(cntProof : CountingProof) { val falseLit = l.BoolLit(false).asInstanceOf[l.Expr] val distincts = E.distinct(rwMaps.map(m => l.Tuple(m.map(p => p._2).toList)).toList : _*) val query = E.forall(qVars, E.implies(ante, E.and(conjunction, distincts))) - val assertStmt = l.AssertStmt(query, None, decorators) + val assertStmt = l.AssertStmt(query, Some(l.Identifier("ConstantBound")), decorators) BlockStmt(blkDecls, List(assertStmt)) } def rewriteConstLb(ufMap : UFMap, st : ConstLbStmt) : List[l.Statement] = { @@ -202,7 +202,7 @@ class UMCRewriter(cntProof : CountingProof) { val f = s1.e val g = s2.e val assertExpr = E.forall(args, E.implies(f, g)) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("UB")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val assumeExpr = E.forall(st.e1.ys, E.le(ufn1, ufn2)) @@ -221,7 +221,7 @@ class UMCRewriter(cntProof : CountingProof) { val f2 = s2.e val f3 = s3.e val assertExpr = E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("AndUB")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val ufn3 = _apply(ufMap(s3)) @@ -241,7 +241,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl = E.implies(f_x, conseq) val qVars = f.xs ++ f.ys val qOp = E.forall(qVars, impl) - val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("Injectivity_SkolemApplication")), List.empty) // Next we want to show injectivity of the skolem: // f(x1) && f(x2) && (x1 != x2) ==> skolem(x1) != skolem(x2) @@ -260,7 +260,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl2 = E.implies(ante2, skdiff) val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("Injectivity_SkolemInjectivity")), List.empty) // Now the assumption. val ufn = _apply(ufMap(f)) @@ -287,7 +287,7 @@ class UMCRewriter(cntProof : CountingProof) { val f2 = s2.e val f3 = s3.e val assertExpr = E.and(E.iff(E.forall(args1, f1), E.and(E.forall(args2, f2), E.forall(args3, f3))), intersection) - val assertStmt = l.AssertStmt(assertExpr, None, List.empty) + val assertStmt = l.AssertStmt(assertExpr, Some(l.Identifier("Disjoint")), List.empty) val ufn1 = _apply(ufMap(s1)) val ufn2 = _apply(ufMap(s2)) val ufn3 = _apply(ufMap(s3)) @@ -310,7 +310,7 @@ class UMCRewriter(cntProof : CountingProof) { val impl = E.implies(ante, conseq) val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys val qOp = E.forall(qVars, impl) - val liftAssertStmt = l.AssertStmt(qOp, None, List.empty) + val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndLB_SkolemApplication")), List.empty) // Now we want to show injectivity of the skolem: // f(x1, n) && g(y1, n) && f(x2, n) && g(y2, n) && (x1 != x2 || y1 != y2) @@ -340,7 +340,7 @@ class UMCRewriter(cntProof : CountingProof) { (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), None, List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) From d7824fd462adbcaa497702722ba41c03e648b212 Mon Sep 17 00:00:00 2001 From: ssahai Date: Mon, 18 May 2020 21:36:10 +0530 Subject: [PATCH 111/119] fixed repetition of quantified variables in indLB --- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 3d025192c..1b2b235c0 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -309,7 +309,7 @@ class UMCRewriter(cntProof : CountingProof) { val conseq = new ExprRewriter(skSubs).rewrite(f_xn) val impl = E.implies(ante, conseq) val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys - val qOp = E.forall(qVars, impl) + val qOp = E.forall(qVars.distinct, impl) val liftAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndLB_SkolemApplication")), List.empty) // Now we want to show injectivity of the skolem: @@ -340,7 +340,7 @@ class UMCRewriter(cntProof : CountingProof) { (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ (g.xs zip y1s).map(p => (p._2, p._1._2)) ++ (g.xs zip y2s).map(p => (p._2, p._1._2)) ++ f.ys ++ g.ys - val injAssertStmt = l.AssertStmt(E.forall(vars, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) + val injAssertStmt = l.AssertStmt(E.forall(vars.distinct, impl2), Some(l.Identifier("IndLB_SkolemInjectivity")), List.empty) // Finally, we have to produce the assumption. val ufn = _apply(ufMap(f)) From 04f97098043b927e92287df92f9444a00d392520 Mon Sep 17 00:00:00 2001 From: ssahai Date: Tue, 19 May 2020 16:24:51 +0530 Subject: [PATCH 112/119] induction working for array shuffle --- test/modelcounter/counting-array-shuffle.ucl | 39 +++++++------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl index 4c7d8e042..049fb38a8 100644 --- a/test/modelcounter/counting-array-shuffle.ucl +++ b/test/modelcounter/counting-array-shuffle.ucl @@ -2,37 +2,26 @@ module main { - define V(Y : [integer]integer, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: 0 <= i < R ==> i <= Y[i] <= R) - && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); - - define Vf(Y : [integer]integer, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> (Y[i] == 0)); - - define W(i : integer, j: integer, R: integer) : boolean = j <= i < R; + define V(Y : [integer]integer, R : integer, N: integer) : boolean = + (0 <= R < N) && (forall (i : integer) :: 0 <= i < R ==> i < Y[i] <= N) + && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); + define W(j : integer, R: integer, N: integer) : boolean = R+1 <= j < N; proof { - // (ConstEq) #Y. V(Y, 1) = 1 - // assert constEq: R == 1 ==> #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) == 1; - - // Proving for Vf (NOT REQUIRED) - // (IndLB) #Y. Vf(Y, R) >= #Y. Vf(Y, R-1) * #i. W(i, j, R) - assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R + 1)) >= - #[(Y: [integer]integer) for (R: integer)] :: (Vf(Y, R)) * - #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) - skolems(Y[(R) -> i]); + // (ConstEq) #Y. V(Y, 1, N) = N + // assert constEq: (R == 1 && N > R) ==> #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) == N; // Proving for V - // (IndLB) #Y. V(Y, R) >= #Y. V(Y, R-1) * #i. W(i, j, R) - assert indLB: #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R + 1)) >= - #[(Y: [integer]integer) for (R: integer)] :: (V(Y, R)) * - #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) - skolems(Y[(R) -> i]); - - // (Range) #i. W(i, j, R) == R - j - assert range: (j > 0 && R > j) ==> #[(i: integer) for (j: integer, R: integer)] :: (W(i, j, R)) == (R - j); + // (IndLB) #Y. V(Y, R+1, N) >= #Y. V(Y, R, N) * #i. W(i, R, N) + assert indLB: #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R + 1, N)) >= + #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) * + #[(j: integer) for (R: integer, N: integer)] :: (W(j, R, N)) + skolems(Y[(R) -> j]); + + // (Range) #i. W(i, R, N) == N - (R + 1) + assert range: (0 < R < N) ==> #[(i: integer) for (R: integer, N: integer)] :: (W(i, R, N)) == (N - (R + 1)); } } From 4beacbc9dfca4d7743365a2114418c4d9670eccc Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Wed, 23 Sep 2020 17:44:32 +0530 Subject: [PATCH 113/119] added test for indLB. Working fine now --- test/modelcounter/test-indlb.ucl | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/modelcounter/test-indlb.ucl diff --git a/test/modelcounter/test-indlb.ucl b/test/modelcounter/test-indlb.ucl new file mode 100644 index 000000000..8dcd9cf67 --- /dev/null +++ b/test/modelcounter/test-indlb.ucl @@ -0,0 +1,11 @@ +module main { + define X(j: integer) : boolean = 0 <= j < 2; + define U(arr: [integer]integer, n: integer): boolean = + (n >= 0) && (forall (i: integer) :: (i < 0 || i >= n) ==> arr[i] == 0) && (forall (i: integer) :: (i >= 0 || i < n) ==> (0 <= arr[i] < 2)); + proof { + assert indLB: #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n+1)) >= + #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n)) * + #[(j : integer) for (n : integer)] :: (X(j)) + skolems (arr[n -> j]); + } +} \ No newline at end of file From 409de53df0b67d1f56f39dca3f3a047175210563 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Thu, 24 Sep 2020 18:24:20 +0530 Subject: [PATCH 114/119] added indUB --- .../extensions/modelcounts/UMCRewriter.scala | 64 +++++++++++++++++++ test/modelcounter/test-indub.ucl | 11 ++++ 2 files changed, 75 insertions(+) create mode 100644 test/modelcounter/test-indub.ucl diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 1b2b235c0..805e7ceb6 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -354,6 +354,68 @@ class UMCRewriter(cntProof : CountingProof) { List(liftAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) } + def rewriteIndUb(ufMap : UFMap, indub : IndUbStmt) : List[l.Statement] = { + // First we want f(x, n) ==> f(skolem_x(x, n), n - 1) && g(skolem_y(x, n), n - 1) + val fp = indub.fp + val f = indub.f + val g = indub.g + val f_xn = f.e + val g_yn = g.e + val ante = fp.e + val skSubs = (f.xs.map(_._1.asInstanceOf[l.Expr]) zip indub.skolems.lift(0)).toMap ++ + (g.xs.map(_._1.asInstanceOf[l.Expr]) zip indub.skolems.lift(1)).toMap + + val conseq_f_subs = new ExprRewriter(skSubs).rewrite(f_xn) + val conseq_g_subs = new ExprRewriter(skSubs).rewrite(g_yn) + + // add base case of induction to consequent. i.e. n+1 == 0 + val nplus1 = E.plus(indub.n, l.IntLit(1)) + val conseq_base = E.eq(nplus1,l.IntLit(0)) + + val conseq = E.or( E.and(conseq_f_subs, conseq_g_subs), conseq_base) + val impl = E.implies(ante, conseq) + val qVars = f.xs ++ g.xs ++ f.ys ++ g.ys + val qOp = E.forall(qVars.distinct, impl) + val lowerAssertStmt = l.AssertStmt(qOp, Some(l.Identifier("IndUB_SkolemApplication")), List.empty) + + // Now we want to show injectivity of the skolem: + // f(x1, n+1) && f(x2, n+1) && (x1 != x2) + // ==> skolem_x(x1, n) != skolem_x(x2,n) || skolem_y(x1,n) != skolem_y(x2, n) + val x1s = fp.xs.map(p => generateId(p._1.toString())) + val x2s = fp.xs.map(p => generateId(p._1.toString())) + + val rwx1 = new ExprRewriter((fp.xs zip x1s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + val rwx2 = new ExprRewriter((fp.xs zip x2s).map(p => (p._1._1.asInstanceOf[l.Expr] -> p._2.asInstanceOf[l.Expr])).toMap) + + val f_x1n = rwx1.rewrite(fp.e) + val f_x2n = rwx2.rewrite(fp.e) + val xdiff = E.orL((x1s zip x2s).map(p => E.distinct(p._1, p._2))) + + val ante2 = E.andL(List(f_x1n, f_x2n, xdiff)) + + val sk_x1 = indub.skolems.map(sk => rwx1.rewrite(sk)) + val sk_x2 = indub.skolems.map(sk => rwx2.rewrite(sk)) + + val skdiff = E.orL((sk_x1 zip sk_x2).map(p => E.distinct(p._1, p._2))) + + val impl2 = E.implies(ante2, skdiff) + + val vars = (f.xs zip x1s).map(p => (p._2, p._1._2)) ++ + (f.xs zip x2s).map(p => (p._2, p._1._2)) ++ f.ys + val injAssertStmt = l.AssertStmt(E.forall(vars.distinct, impl2), Some(l.Identifier("IndUB_SkolemInjectivity")), List.empty) + + // Finally, we have to produce the assumption. + val ufn = _apply(ufMap(f)) + val ugn = _apply(ufMap(g)) + val ufnplus1 = E.apply(ufMap(f).id, List(nplus1) ++ ufMap(f).sig.args.drop(1).map(_._1)) + val geqExpr = E.le(ufnplus1, E.mul(ufn, ugn)) + val assumpStmt1 = l.AssumeStmt(E.forall(f.ys ++ g.ys, geqExpr), None) + val ufpn = _apply(ufMap(indub.fp)) + val eqExpr = E.eq(ufnplus1, ufpn) + val assumpStmt2 = l.AssumeStmt(E.forall(f.ys, eqExpr), None) + List(lowerAssertStmt, injAssertStmt, assumpStmt1, assumpStmt2) + } + def rewriteAssert(ufmap : UFMap, st : Statement) : List[l.Statement] = { val newStmts : List[l.Statement] = st match { @@ -380,6 +442,8 @@ class UMCRewriter(cntProof : CountingProof) { rewriteDisjoint(ufmap, disj) case inj : InjectivityStmt => rewriteInjectivity(ufmap, inj) + case indUb : IndUbStmt => + rewriteIndUb(ufmap, indUb) case _ => throw new AssertionError("Unknown proof statement: " + st.toString()) } diff --git a/test/modelcounter/test-indub.ucl b/test/modelcounter/test-indub.ucl new file mode 100644 index 000000000..1f3418c45 --- /dev/null +++ b/test/modelcounter/test-indub.ucl @@ -0,0 +1,11 @@ +module main { + define X(j: integer) : boolean = 0 <= j < 2; + define U(arr: [integer]integer, n: integer): boolean = + (n >= 0) && (forall (i: integer) :: (i < 0 || i >= n) ==> arr[i] == 0) && (forall (i: integer) :: (i >= 0 || i < n) ==> (0 <= arr[i] < 2)); + proof { + assert indUB: #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n+1)) <= + #[(arr: [integer]integer) for (n : integer)] :: (U(arr, n)) * + #[(j : integer) for (n : integer)] :: (X(j)) + skolems (arr[n -> 0], arr[n]); + } +} \ No newline at end of file From 9b0eb83e5f1c78b76598eed874eee32b6ce739c2 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Fri, 25 Sep 2020 11:21:51 +0530 Subject: [PATCH 115/119] updated/added tests for all the proof rules --- test/modelcounter/counting-array-shuffle.ucl | 27 -------- test/modelcounter/counting-zkhat.ucl | 44 ------------- test/modelcounter/counting_2n.ucl | 36 ---------- test/modelcounter/debug2.ucl | 65 ------------------- test/modelcounter/test-constLB.ucl | 9 +++ test/modelcounter/test-constUB.ucl | 6 ++ .../{test-indlb.ucl => test-indLB.ucl} | 0 .../{test-indub.ucl => test-indUB.ucl} | 2 +- test/modelcounter/test-lemma.ucl | 21 ------ test/modelcounter/{hello.ucl => test-or.ucl} | 5 -- test/modelcounter/test-range.ucl | 8 +++ 11 files changed, 24 insertions(+), 199 deletions(-) delete mode 100644 test/modelcounter/counting-array-shuffle.ucl delete mode 100644 test/modelcounter/counting-zkhat.ucl delete mode 100644 test/modelcounter/counting_2n.ucl delete mode 100644 test/modelcounter/debug2.ucl create mode 100644 test/modelcounter/test-constLB.ucl create mode 100644 test/modelcounter/test-constUB.ucl rename test/modelcounter/{test-indlb.ucl => test-indLB.ucl} (100%) rename test/modelcounter/{test-indub.ucl => test-indUB.ucl} (99%) delete mode 100644 test/modelcounter/test-lemma.ucl rename test/modelcounter/{hello.ucl => test-or.ucl} (58%) create mode 100644 test/modelcounter/test-range.ucl diff --git a/test/modelcounter/counting-array-shuffle.ucl b/test/modelcounter/counting-array-shuffle.ucl deleted file mode 100644 index 049fb38a8..000000000 --- a/test/modelcounter/counting-array-shuffle.ucl +++ /dev/null @@ -1,27 +0,0 @@ -// Model counting in Array Shuffle - -module main { - - define V(Y : [integer]integer, R : integer, N: integer) : boolean = - (0 <= R < N) && (forall (i : integer) :: 0 <= i < R ==> i < Y[i] <= N) - && (forall (i : integer) :: (i < 0 || i >= R) ==> Y[i] == 0); - - define W(j : integer, R: integer, N: integer) : boolean = R+1 <= j < N; - - proof { - - // (ConstEq) #Y. V(Y, 1, N) = N - // assert constEq: (R == 1 && N > R) ==> #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) == N; - - // Proving for V - // (IndLB) #Y. V(Y, R+1, N) >= #Y. V(Y, R, N) * #i. W(i, R, N) - assert indLB: #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R + 1, N)) >= - #[(Y: [integer]integer) for (R: integer, N: integer)] :: (V(Y, R, N)) * - #[(j: integer) for (R: integer, N: integer)] :: (W(j, R, N)) - skolems(Y[(R) -> j]); - - // (Range) #i. W(i, R, N) == N - (R + 1) - assert range: (0 < R < N) ==> #[(i: integer) for (R: integer, N: integer)] :: (W(i, R, N)) == (N - (R + 1)); - } -} - diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl deleted file mode 100644 index 2d14c0701..000000000 --- a/test/modelcounter/counting-zkhat.ucl +++ /dev/null @@ -1,44 +0,0 @@ -// Model counting in ZK Hats - Motivating example in CAV 2020 paper - -module main { - - define V(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) - && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); - - define Vf(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); - - define V1(Y : [integer]boolean, R : integer) : boolean = - (R >= 0) && (forall (i : integer) :: !Y[i]); - - define W(i : integer) : boolean = 0 <= i < 2; - - proof { - // Proof rules 2 - 7 from paper. - - // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) - assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == - #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + - #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); - - // 3. (ConstEq) #Y. V1(Y, R) = 1 - assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; - - // 4. (ConstEq) #Y. Vf(Y, 1) = 2 - assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; - - // 5. (IndUB) #Y. Vf(Y, R) <= #i.W(i) * #Y. Vf(Y, R-1) - // TODO - - // 6. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) - assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= - #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * - #[(i: integer) for (R: integer)] :: (W(i)) - skolems(Y[(R) -> (i == 1)]); - - // 7. (Range) #i. W(i) == 2 - assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; - - } -} diff --git a/test/modelcounter/counting_2n.ucl b/test/modelcounter/counting_2n.ucl deleted file mode 100644 index d270863e8..000000000 --- a/test/modelcounter/counting_2n.ucl +++ /dev/null @@ -1,36 +0,0 @@ -module main { - - // U is the function V_f in the paper. - define U(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]); - // V is the function V in the paper. - define V(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: (i < 0 || i >= n) ==> !arr[i]) && - (exists (i : integer) :: 0 <= i < n && arr[i]); - // W is the function V_1 in the paper. - define W(arr : [integer]boolean, n : integer) : boolean = - (n >= 0) && (forall (i : integer) :: !arr[i]); - define X(b : boolean) : boolean = true; - - proof { - // The satisfying assignments to U are the disjoint union of the satisfying - // assignments to V and W. - assert or: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == - #[(arr: [integer]boolean) for (n : integer)] :: (V(arr, n)) + - #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)); - // prove #arr: W(arr, n) == 1 - assert constEq: (n >= 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (W(arr, n)) == 1; - // prove #arr: U(arr, 1) == 1 - assert constEq: (n == 0) ==> #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) == 1; - // prove #i: X(b) == 2 - assert constEq: #[(b : boolean) for (n : integer)] :: (X(b)) == 2; - assert forall (n : integer) :: (#[(b : boolean) for (n : integer)] :: (X(b))) == 2; - // prove the inductive step in the count. - // #arr. U(arr, n + 1) >= #arr. U(arr, n) * 2 - assert indLB: #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n+1)) >= - #[(arr: [integer]boolean) for (n : integer)] :: (U(arr, n)) * - #[(b : boolean) for (n : integer)] :: (X(b)) - skolems (arr[n -> b]); - } -} - diff --git a/test/modelcounter/debug2.ucl b/test/modelcounter/debug2.ucl deleted file mode 100644 index 7650c33ad..000000000 --- a/test/modelcounter/debug2.ucl +++ /dev/null @@ -1,65 +0,0 @@ - module main { - define V (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))); - define Vf (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))); - define V1 (Y: [integer]boolean, R: integer): boolean = ((R >= 0) && (forall (i : integer) :: !((Y)[i]))); - define W (i: integer): boolean = ((0 <= i) && (i < 2)); - function count_1(R: integer): integer; // line 0 - axiom assump_1 : (forall (R : integer) :: (count_1(R) >= 0)); // - function count_2(R: integer): integer; // line 0 - axiom assump_2 : (forall (R : integer) :: (count_2(R) >= 0)); // - function count_3(R: integer): integer; // line 0 - axiom assump_3 : (forall (R : integer) :: (count_3(R) >= 0)); // - function count_4(R: integer): integer; // line 0 - axiom assump_4 : (forall (R : integer) :: (count_4(R) >= 0)); // - function count_5(R: integer): integer; // line 0 - axiom assump_5 : (forall (R : integer) :: (count_5(R) >= 0)); // - procedure countingProof() returns () - { - assert ((forall (Y : [integer]boolean, R : integer) :: (((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) <==> (((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) || ((R >= 0) && (forall (i : integer) :: !((Y)[i])))))) && (forall (Y : [integer]boolean, R : integer) :: !((((R >= 0) && ((exists (i : integer) :: (((0 <= i) && (i < R)) && (Y)[i])) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i]))))) && ((R >= 0) && (forall (i : integer) :: !((Y)[i]))))))); // line 0 - assume (forall (R : integer) :: (count_4(R) == (count_1(R) + count_5(R)))); // line 0 - { - var Y_1 : [integer]boolean; // line 26 - var R : integer; // line 26 - assert [cover, SATOnly]: (forall (R : integer) :: (R >= 0 ==> (R >= 0 && (forall (i : integer) :: !Y_1[i])))); // line 0 - } - assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) >= 1))); // line 0 - { - var Y_2 : [integer]boolean; // line 26 - var Y_3 : [integer]boolean; // line 26 - var R : integer; // line 26 - assert [cover]: (forall (R : integer) :: ((R >= 0) ==> (((true && ((R >= 0) && (forall (i : integer) :: !((Y_2)[i])))) && ((R >= 0) && (forall (i : integer) :: !((Y_3)[i])))) && distinct({Y_2}, {Y_3})))); // line 0 - } - assume (forall (R : integer) :: ((R >= 0) ==> (count_5(R) < 2))); // line 0 - { - var Y_4 : [integer]boolean; // line 29 - var Y_5 : [integer]boolean; // line 29 - var R : integer; // line 29 - assert [cover, SATOnly]: (forall (R : integer) :: ((R == 1) ==> (((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_4)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_5)[i]))))) && distinct({Y_4}, {Y_5})))); // line 0 - } - assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) >= 2))); // line 0 - { - var Y_6 : [integer]boolean; // line 29 - var Y_7 : [integer]boolean; // line 29 - var Y_8 : [integer]boolean; // line 29 - var R : integer; // line 29 - assert [cover]: (forall (R : integer) :: ((R == 1) ==> ((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_6)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_7)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_8)[i]))))) && distinct({Y_6}, {Y_7}, {Y_8})))); // line 0 - } - assume (forall (R : integer) :: ((R == 1) ==> (count_4(R) < 3))); // line 0 - assert (forall (Y : [integer]boolean, i : integer, R : integer) :: ((((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y)[i])))) && ((0 <= i) && (i < 2))) ==> (((R + 1) >= 0) && (forall (i : integer) :: (((i < 0) || (i >= (R + 1))) ==> !(((Y)[R -> (i == 1)])[i])))))); // line 0 - assert (forall (Y_9 : [integer]boolean, Y_10 : [integer]boolean, i_1 : integer, i_2 : integer, R : integer) :: ((((((true && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_9)[i]))))) && ((R >= 0) && (forall (i : integer) :: (((i < 0) || (i >= R)) ==> !((Y_10)[i]))))) && ((0 <= i_1) && (i_1 < 2))) && ((0 <= i_2) && (i_2 < 2))) && ((false || distinct(Y_9, Y_10)) || (false || distinct(i_1, i_2)))) ==> (false || distinct((Y_9)[R -> (i_1 == 1)], (Y_10)[R -> (i_2 == 1)])))); // line 0 - assume (forall (R : integer) :: (count_4((R + 1)) >= (count_4(R) * count_3(R)))); // line 0 - assume (forall (R : integer) :: (count_4((R + 1)) == count_2(R))); // line 0 - assume (forall (R : integer) :: (count_3(R) == (if(0 < 2) then 2 else 0))); // line 0 - assert (forall (R : integer) :: (count_3(R) == 2)); // line 0 - - assert (forall (R : integer) :: count_4(R+1) >= count_4(R) * 2); - assert (count_4(1) >= 2); - assert (forall (R : integer) :: (R >= 0) ==> (count_1(R) == count_4(R) - 1)); - } - control { - v = verify(countingProof /* countingProof*/); // line 0 - check; // line 0 - print_results; // line 0 - } - } - diff --git a/test/modelcounter/test-constLB.ucl b/test/modelcounter/test-constLB.ucl new file mode 100644 index 000000000..95bed7400 --- /dev/null +++ b/test/modelcounter/test-constLB.ucl @@ -0,0 +1,9 @@ +module main { + + define f(n : integer) : boolean = 1 <= n < 10; + + proof { + assert constLB: #[(n:integer)] :: (f(n)) >= 5; + assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; + } +} diff --git a/test/modelcounter/test-constUB.ucl b/test/modelcounter/test-constUB.ucl new file mode 100644 index 000000000..131ca6c88 --- /dev/null +++ b/test/modelcounter/test-constUB.ucl @@ -0,0 +1,6 @@ +module main { + + proof { + assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; + } +} diff --git a/test/modelcounter/test-indlb.ucl b/test/modelcounter/test-indLB.ucl similarity index 100% rename from test/modelcounter/test-indlb.ucl rename to test/modelcounter/test-indLB.ucl diff --git a/test/modelcounter/test-indub.ucl b/test/modelcounter/test-indUB.ucl similarity index 99% rename from test/modelcounter/test-indub.ucl rename to test/modelcounter/test-indUB.ucl index 1f3418c45..0d0e4eba8 100644 --- a/test/modelcounter/test-indub.ucl +++ b/test/modelcounter/test-indUB.ucl @@ -8,4 +8,4 @@ module main { #[(j : integer) for (n : integer)] :: (X(j)) skolems (arr[n -> 0], arr[n]); } -} \ No newline at end of file +} diff --git a/test/modelcounter/test-lemma.ucl b/test/modelcounter/test-lemma.ucl deleted file mode 100644 index 8dbfe32b7..000000000 --- a/test/modelcounter/test-lemma.ucl +++ /dev/null @@ -1,21 +0,0 @@ -module main { - define f (n: integer): boolean = 1 <= n < 10; - - lemmas{ - lemma lemma1() - returns (b: boolean) - { - b= true; - } - lemma lemma2() - returns (i: integer) - ensures i > 10; - { - i= 42; - } - } - - proof { - assert range: #[(n: integer)] :: (f(n)) == 9; - } -} \ No newline at end of file diff --git a/test/modelcounter/hello.ucl b/test/modelcounter/test-or.ucl similarity index 58% rename from test/modelcounter/hello.ucl rename to test/modelcounter/test-or.ucl index 95c10e6b9..e2fc2b748 100644 --- a/test/modelcounter/hello.ucl +++ b/test/modelcounter/test-or.ucl @@ -5,12 +5,7 @@ module main { define h(n : integer) : boolean = 5 <= n < 11; proof { - assert range: #[(n:integer)] :: (f(n)) == 16; - assert constLB: #[(n:integer)] :: (g(n)) >= 5; - assert constLB: #[(a:bv1, b:bv1)] :: (a == b) >= 2; - assert constUB: #[(a:bv1, b:bv1)] :: (a == b) < 3; assert or: #[(n:integer) for ()] :: (f(n)) == #[(n:integer)] :: (g(n)) + #[(n:integer)] :: (h(n)); } } - diff --git a/test/modelcounter/test-range.ucl b/test/modelcounter/test-range.ucl new file mode 100644 index 000000000..4a25a259a --- /dev/null +++ b/test/modelcounter/test-range.ucl @@ -0,0 +1,8 @@ +module main { + + define f(n : integer) : boolean = 1 <= n < 21; + + proof { + assert range: #[(n:integer)] :: (f(n)) == 20; + } +} From eefb68b698f2302722f04389425f857ad6ef7dd7 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Fri, 25 Sep 2020 11:30:00 +0530 Subject: [PATCH 116/119] minor edit to fix warning while building the universal package --- src/main/scala/uclid/smt/SMTLIB2Interface.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/uclid/smt/SMTLIB2Interface.scala b/src/main/scala/uclid/smt/SMTLIB2Interface.scala index 613054e88..b0670f02d 100644 --- a/src/main/scala/uclid/smt/SMTLIB2Interface.scala +++ b/src/main/scala/uclid/smt/SMTLIB2Interface.scala @@ -170,7 +170,7 @@ trait SMTLIB2Base { /** * Translates an smt operator to its string representation. * - * @opIn The smt operator to be translated. + * @param opIn The smt operator to be translated. */ def translateOp(opIn: Operator) : String = { opIn match { From 8c9832ca67ccba5ebf3bd90cec7e2dbed0ec431b Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Wed, 30 Sep 2020 15:01:54 +0530 Subject: [PATCH 117/119] update author info in modelcounting extension --- src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCMain.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCParser.scala | 2 +- src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala index 07d54895a..b82f370ad 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCLanguage.scala @@ -30,7 +30,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Main file for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index 45232ec69..bf92782a8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -30,7 +30,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Main file for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala index 1f80cd9e8..4d63b6a8b 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCParser.scala @@ -31,7 +31,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Parser for the UCLID model counter. * diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala index 805e7ceb6..3fbc78359 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCRewriter.scala @@ -31,7 +31,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * Author: Pramod Subramanyan + * Author: Pramod Subramanyan, Shubham Sahai * * Rewriter for the UCLID5 model counter. * From aa89b1a9c41da60217fabb68b332728e8ed05517 Mon Sep 17 00:00:00 2001 From: Shubham Sahai Date: Thu, 1 Oct 2020 13:08:31 +0530 Subject: [PATCH 118/119] config options works with UMC now --- src/main/scala/uclid/extensions/modelcounts/UMCMain.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala index bf92782a8..133fa32e8 100644 --- a/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala +++ b/src/main/scala/uclid/extensions/modelcounts/UMCMain.scala @@ -44,8 +44,7 @@ import uclid.Utils object UMCMain { /** Executes regular UCLID5 on the processed module. */ - def runProcessedModel(module : l.Module) : Unit = { - val config = UclidMain.Config() + def runProcessedModel(module : l.Module, config: UclidMain.Config) : Unit = { val mainModuleName = l.Identifier("main") val modules = UclidMain.compileModules(List(module), mainModuleName, false) val mainModule = UclidMain.instantiate(config, modules, mainModuleName, true) @@ -55,7 +54,7 @@ object UMCMain { throw new Utils.ParserError("Unable to find main module", None, None) } } - + def checkModel(f: java.io.File, config: UclidMain.Config) { val module = UMCParser.parseUMCModel(f) println("Parsed module: " + module.id.toString()) @@ -63,6 +62,6 @@ object UMCMain { val moduleP = new UMCRewriter(module).process() println("\nModule after rewriting: ") println(moduleP.toString()) - runProcessedModel(moduleP) + runProcessedModel(moduleP, config) } } From b6677d090ac6d29a98fd4eed289ad9d4d2af8237 Mon Sep 17 00:00:00 2001 From: ssahai Date: Fri, 30 Oct 2020 21:45:43 +0530 Subject: [PATCH 119/119] added counting script for zkHAT - motivating example from CAV20 paper --- test/modelcounter/counting-zkhat.ucl | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/modelcounter/counting-zkhat.ucl diff --git a/test/modelcounter/counting-zkhat.ucl b/test/modelcounter/counting-zkhat.ucl new file mode 100644 index 000000000..9b351dd0e --- /dev/null +++ b/test/modelcounter/counting-zkhat.ucl @@ -0,0 +1,41 @@ +// Model counting in ZK Hats - Motivating example in CAV 2020 paper + +module main { + + define V(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (exists (i : integer) :: 0 <= i < R && Y[i]) + && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define Vf(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: (i < 0 || i >= R) ==> !Y[i]); + + define V1(Y : [integer]boolean, R : integer) : boolean = + (R >= 0) && (forall (i : integer) :: !Y[i]); + + define W(i : integer) : boolean = 0 <= i < 2; + + proof { + // Proof rules 2 - 7 from paper. + + // 2. (Or) #Y. Vf(Y, R) = #Y. V(Y, R) + #Y.(V1(Y,R)) + assert or: #[(Y: [integer]boolean) for (R : integer)] :: (Vf(Y, R)) == + #[(Y: [integer]boolean) for (R : integer)] :: (V(Y, R)) + + #[(Y: [integer]boolean) for (R : integer)] :: (V1(Y, R)); + + // 3. (ConstEq) #Y. V1(Y, R) = 1 + assert constEq: R >= 0 ==> #[(Y: [integer]boolean) for (R: integer)] :: (V1(Y, R)) == 1; + + // 4. (ConstEq) #Y. Vf(Y, 1) = 2 + assert constEq: R == 1 ==> #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) == 2; + + // 5. (IndLB) #Y. Vf(Y, R) >= #i.W(i) * #Y. Vf(Y, R-1) + assert indLB: #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R + 1)) >= + #[(Y: [integer]boolean) for (R: integer)] :: (Vf(Y, R)) * + #[(i: integer) for (R: integer)] :: (W(i)) + skolems(Y[(R) -> (i == 1)]); + + // 6. (Range) #i. W(i) == 2 + assert range: #[(i: integer) for (R: integer)] :: (W(i)) == 2; + + } +}