-
Notifications
You must be signed in to change notification settings - Fork 52
Backend support for SMTLib types (particularly bitvectors and floats) #428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
16c0299
071af4a
d77ffc9
45adbed
0b2718c
4c0d52c
4c2e467
075fd4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -624,6 +624,7 @@ object FastPrettyPrinter extends FastPrettyPrinterBase with BracketPrettyPrinter | |
| case dt@DomainType(domainName, typVarsMap) => | ||
| val typArgs = dt.typeParameters map (t => show(typVarsMap.getOrElse(t, t))) | ||
| text(domainName) <> (if (typArgs.isEmpty) nil else brackets(ssep(typArgs, char (',') <> space))) | ||
| case SMTType(boogieName, _) => boogieName | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it really the Boogie name we want to use? Seems odd, since the extension is about SMT types.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I figured the Boogie name would be less confusing because SMT type names can look a bit weird.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, the SMT type name is really no eye candy. Does the parser also expect the Boogie name format? Maybe we could only use the Boogie type names, and backends (Silicon) would have to map them to SMT, if necessary, potentially with the help of some Silver function.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is currently only in the AST for frontends to use, there is no support for this on the source level, the parser doesn't know about this. Use only Boogie names: Yeah sure, we could do that. Then we would lose the option of using types that exists in Z3 but not in Boogie, though.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we wanted to merge more or less as-is, we could probably describe the feature as (very) experimental, and see what happens. Or just not document it at all :-) |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -770,11 +771,12 @@ object FastPrettyPrinter extends FastPrettyPrinterBase with BracketPrettyPrinter | |
| text(funcname) <> parens(ssep(args map show, char (',') <> space)) | ||
| case dfa@DomainFuncApp(funcname, args, tvMap) => | ||
| if (tvMap.nonEmpty) | ||
| // Type may be underconstrained, so to be safe we explicitly print out the type. | ||
| // Type may be underconstrained, so to be safe we explicitly print out the type. | ||
| parens(text(funcname) <> parens(ssep(args map show, char (',') <> space)) <> char(':') <+> show(dfa.typ)) | ||
| else | ||
| text(funcname) <> parens(ssep(args map show, char (',') <> space)) | ||
|
|
||
| case SMTFuncApp(func, args) => | ||
| text(func.name) <> parens(ssep(args map show, char(',') <> space)) | ||
| case EmptySeq(elemTyp) => | ||
| text("Seq[") <> showType(elemTyp) <> "]()" | ||
| case ExplicitSeq(elems) => | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package viper.silver.ast.utility | ||
|
|
||
| import viper.silver.ast.{Bool, Int, LocalVarDecl, SMTFunc, SMTType} | ||
|
|
||
| /** | ||
| * A factory for fixed-size bitvectors that offers convenient access to bitvector types, as well as | ||
| * function definitions for unary and binary functions on bitvectors, as well as conversions from and | ||
| * to integers. | ||
| */ | ||
| case class BVFactory(size: Int) { | ||
| lazy val typ = SMTType(s"bv${size}", s"(_ BitVec ${size})") | ||
|
|
||
| def xor(name: String) = SMTFunc(name, "bvxor", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def and(name: String) = SMTFunc(name, "bvand", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def or(name: String) = SMTFunc(name, "bvor", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def add(name: String) = SMTFunc(name, "bvadd", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def mul(name: String) = SMTFunc(name, "bvmul", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def shl(name: String) = SMTFunc(name, "bvshl", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def shr(name: String) = SMTFunc(name, "bvshr", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
|
|
||
| def not(name: String) = SMTFunc(name, "bvnot", typ, Seq(LocalVarDecl("x", typ)())) | ||
| def neg(name: String) = SMTFunc(name, "bvneg", typ, Seq(LocalVarDecl("x", typ)())) | ||
|
|
||
| def from_int(name: String) = SMTFunc(name, s"(_ int2bv ${size})", typ, Seq(LocalVarDecl("x", Int)())) | ||
| def to_int(name: String) = SMTFunc(name, s"(_ bv2int ${size})", Int, Seq(LocalVarDecl("x", typ)())) | ||
| def from_nat(name: String) = SMTFunc(name, s"(_ nat2bv ${size})", typ, Seq(LocalVarDecl("x", Int)())) | ||
| def to_nat(name: String) = SMTFunc(name, s"(_ bv2nat ${size})", Int, Seq(LocalVarDecl("x", typ)())) | ||
| } | ||
|
|
||
| /** | ||
| * Rounding modes for floating point operations. | ||
| */ | ||
| object RoundingMode extends Enumeration { | ||
| type RoundingMode = Value | ||
| val RNE, RNA, RTP, RTN, RTZ = Value | ||
| } | ||
| import RoundingMode._ | ||
|
|
||
| /** | ||
| * A factory for IEEE floating point numbers with "exp" bits for the exponent, "mant" bits for the significant, | ||
| * including the hidden bit, and a given rounding mode for all operations that use one. | ||
| * Offers access to types, unary and binary operations, comparisons, and conversions from and to | ||
| * bitvectors of size exp + mant. | ||
| */ | ||
| case class FloatFactory(mant: Int, exp: Int, roundingMode: RoundingMode) { | ||
|
|
||
| lazy val typ = SMTType(s"float${mant}e${exp}", s"(_ FloatingPoint ${exp} ${mant})") | ||
|
|
||
| def neg(name: String) = SMTFunc(name, "fp.neg", typ, Seq(LocalVarDecl("x", typ)())) | ||
| def abs(name: String) = SMTFunc(name, "fp.abs", typ, Seq(LocalVarDecl("x", typ)())) | ||
|
|
||
| def add(name: String) = SMTFunc(name, s"fp.add ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def sub(name: String) = SMTFunc(name, s"fp.sub ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def mul(name: String) = SMTFunc(name, s"fp.mul ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def div(name: String) = SMTFunc(name, s"fp.div ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def min(name: String) = SMTFunc(name, s"fp.min ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def max(name: String) = SMTFunc(name, s"fp.max ${roundingMode}", typ, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
|
|
||
| def eq(name: String) = SMTFunc(name, s"fp.eq", Bool, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def leq(name: String) = SMTFunc(name, s"fp.leq", Bool, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def geq(name: String) = SMTFunc(name, s"fp.geq", Bool, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def lt(name: String) = SMTFunc(name, s"fp.lt", Bool, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
| def gt(name: String) = SMTFunc(name, s"fp.gt", Bool, Seq(LocalVarDecl("x", typ)(), LocalVarDecl("y", typ)())) | ||
|
|
||
| def isZero(name: String) = SMTFunc(name, s"fp.isZero", Bool, Seq(LocalVarDecl("x", typ)())) | ||
| def isInfinite(name: String) = SMTFunc(name, s"fp.isInfinite", Bool, Seq(LocalVarDecl("x", typ)())) | ||
| def isNaN(name: String) = SMTFunc(name, s"fp.isNaN", Bool, Seq(LocalVarDecl("x", typ)())) | ||
| def isNegative(name: String) = SMTFunc(name, s"fp.isNegative", Bool, Seq(LocalVarDecl("x", typ)())) | ||
| def isPositive(name: String) = SMTFunc(name, s"fp.isPositive", Bool, Seq(LocalVarDecl("x", typ)())) | ||
|
|
||
| def from_bv(name: String) = SMTFunc(name, s"(_ to_fp ${exp} ${mant}) ", typ, Seq(LocalVarDecl("x", BVFactory(mant+exp).typ)())) | ||
| def to_bv(name: String) = SMTFunc(name, s"(_ fp.to_sbv ${exp+mant}) ${roundingMode} ", BVFactory(mant+exp).typ, Seq(LocalVarDecl("x", typ)())) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| package viper.silver.testing | ||
|
|
||
| import org.scalatest.{BeforeAndAfterAllConfigMap, ConfigMap, FunSuite, Matchers} | ||
| import viper.silver.ast.{AnySetContains, Assert, EqCmp, Exp, Field, FieldAccess, FieldAccessPredicate, FullPerm, Function, Inhale, IntLit, LocalVarAssign, LocalVarDecl, Method, Program, Ref, Result, SMTFuncApp, Seqn, SetType, Stmt} | ||
| import viper.silver.ast.utility.{BVFactory, FloatFactory, RoundingMode} | ||
| import viper.silver.verifier.{Failure, Success, Verifier} | ||
| import viper.silver.verifier.errors.{AssertFailed, PostconditionViolated} | ||
|
|
||
| trait SMTTypeTest extends FunSuite with Matchers with BeforeAndAfterAllConfigMap { | ||
|
|
||
| def generateTypeCombinationTest(success: Boolean) : (Program, Assert) = { | ||
| val t = if (success) BVFactory(23).typ else FloatFactory(23, 11, RoundingMode.RNE).typ | ||
| val p1_decl = LocalVarDecl("three", t)() | ||
| val p1_ref = p1_decl.localVar | ||
| val p2_decl = LocalVarDecl("lol", SetType(t))() | ||
| val p2_ref= p2_decl.localVar | ||
| val element_in_param = AnySetContains(p1_ref, p2_ref)() | ||
|
|
||
| val assume = Inhale(element_in_param)() | ||
| val assert = Assert(element_in_param)() | ||
| val body = if (success) Seq(assume, assert) else Seq(assert) | ||
| (wrapInProgram(body, Seq(p1_decl, p2_decl), Seq()), assert) | ||
| } | ||
|
|
||
| def generateFieldTypeTest(success: Boolean) : (Program, Assert) = { | ||
| val t = if (!success) BVFactory(23).typ else FloatFactory(23, 11, RoundingMode.RNE).typ | ||
| val field = Field("f", t)() | ||
| val p1_decl = LocalVarDecl("three", Ref)() | ||
| val p1_ref = p1_decl.localVar | ||
| val p2_decl = LocalVarDecl("lol", SetType(t))() | ||
| val p2_ref= p2_decl.localVar | ||
| val fieldAcc = FieldAccess(p1_ref, field)() | ||
| val perm = FieldAccessPredicate(fieldAcc, FullPerm()())() | ||
| val element_in_param = AnySetContains(fieldAcc, p2_ref)() | ||
|
|
||
| val getPerm = Inhale(perm)() | ||
| val assume = Inhale(element_in_param)() | ||
| val assert = Assert(element_in_param)() | ||
| val body = if (success) Seq(getPerm, assume, assert) else Seq(getPerm, assert) | ||
| (wrapInProgram(body, Seq(p1_decl, p2_decl), Seq(), fields = Seq(field)), assert) | ||
| } | ||
|
|
||
| def generateFloatOpTest(success: Boolean) : (Program, Assert) = { | ||
| val rne = RoundingMode.RNE | ||
| val fp = FloatFactory(24, 8, rne) | ||
| val first = 1081081856 // 3.75 | ||
| val second = 1103888384 // 25.5 | ||
| val result = 1105854464 // 29.25 | ||
| val bv32 = BVFactory(32) | ||
| val from_int = bv32.from_int("toBV32") | ||
| val to_fp = fp.from_bv("tofp") | ||
| val fp_eq = fp.eq("fp_eq") | ||
| val fp_add = fp.add("fp_add") | ||
|
|
||
| val first_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(first)()))()))() | ||
| val second_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(second)()))()))() | ||
| val result_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(result)()))()))() | ||
|
|
||
| val zero_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(0)()))()))() | ||
|
|
||
| val addition = SMTFuncApp(fp_add, Seq(first_float, second_float))() | ||
| val result_addition = SMTFuncApp(fp_add, Seq(result_float, if (success) zero_float else first_float))() | ||
|
|
||
| val equality = SMTFuncApp(fp_eq, Seq(addition, result_addition))() | ||
| val assert = Assert(equality)() | ||
| (wrapInProgram(Seq(assert), Seq(), Seq()), assert) | ||
| } | ||
|
|
||
| def generateFloatOpFunctionTest(success: Boolean) : (Program, Function, Exp) = { | ||
| val rne = RoundingMode.RNE | ||
| val fp = FloatFactory(24, 8, rne) | ||
| val first = 1081081856 // 3.75 | ||
| val second = 1103888384 // 25.5 | ||
| val result = 1105854464 // 29.25 | ||
| val bv32 = BVFactory(32) | ||
| val from_int = bv32.from_int("toBV32") | ||
| val to_fp = fp.from_bv("tofp") | ||
| val fp_eq = fp.eq("fp_eq") | ||
| val fp_add = fp.add("fp_add") | ||
|
|
||
| val first_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(first)()))()))() | ||
| val second_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(second)()))()))() | ||
| val result_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(result)()))()))() | ||
|
|
||
| val zero_float = SMTFuncApp(to_fp, Seq(SMTFuncApp(from_int, Seq(IntLit(0)()))()))() | ||
|
|
||
| val addition = SMTFuncApp(fp_add, Seq(first_float, second_float))() | ||
| val result_addition = SMTFuncApp(fp_add, Seq(result_float, if (success) zero_float else first_float))() | ||
|
|
||
| val equality = SMTFuncApp(fp_eq, Seq(Result(fp.typ)(), result_addition))() | ||
|
|
||
| val fun = Function("test", Seq(), fp.typ, Seq(), Seq(equality), Some(addition))() | ||
| val program = Program(Seq(), Seq(), Seq(fun), Seq(), Seq(), Seq())() | ||
| (program, fun, equality) | ||
| } | ||
|
|
||
| def generateBvOpTest(success: Boolean) : (Program, Assert) = { | ||
| val bv23 = BVFactory(23) | ||
| val from_int = bv23.from_int("toBV23") | ||
| val two_lit = IntLit(2)() | ||
| val three_lit = IntLit(3)() | ||
| val one_lit = IntLit(1) () | ||
| val two = SMTFuncApp(from_int, Seq(two_lit))() | ||
| val three = SMTFuncApp(from_int, Seq(three_lit))() | ||
| val one = SMTFuncApp(from_int, Seq(one_lit))() | ||
| val result_decl = LocalVarDecl("three", bv23.typ)() | ||
| val result_ref = result_decl.localVar | ||
| val assign = LocalVarAssign(result_ref, if (success) three else one)() | ||
| val xor = bv23.xor("xorBV23") | ||
| val xor_app = SMTFuncApp(xor, Seq(one, two))() | ||
| val equality1 = EqCmp(result_ref, xor_app)() | ||
| val assertion1 = Assert(equality1)() | ||
| (wrapInProgram(Seq(assign, assertion1), Seq(), Seq(result_decl)), assertion1) | ||
| } | ||
|
|
||
| def wrapInProgram(stmts: Seq[Stmt], params: Seq[LocalVarDecl], vars: Seq[LocalVarDecl], fields: Seq[Field] = Seq()): Program = { | ||
| val block = Seqn(stmts, vars)() | ||
| val method = Method("test", params, Seq(), Seq(), Seq(), Some(block))() | ||
| Program(Seq(), fields, Seq(), Seq(), Seq(method), Seq())() | ||
| } | ||
|
|
||
| val verifier : Verifier | ||
|
|
||
| override def beforeAll(configMap: ConfigMap) { | ||
| verifier.parseCommandLine(Seq("dummy.vpr")) | ||
| verifier.start() | ||
| } | ||
|
|
||
| override def afterAll(configMap: ConfigMap) { | ||
| verifier.stop() | ||
| } | ||
|
|
||
| test("typeCombinationSuccess") { | ||
| val (prog, assertNode) = generateTypeCombinationTest(true) | ||
| val res = verifier.verify(prog) | ||
| assert(res == Success) | ||
| } | ||
|
|
||
| test("typeCombinationFail") { | ||
| val (prog, assertNode) = generateTypeCombinationTest(false) | ||
| val res = verifier.verify(prog) | ||
| assert(res match { | ||
| case Failure(Seq(AssertFailed(a, _, _))) if a == assertNode => true | ||
| case _ => false | ||
| }) | ||
| } | ||
|
|
||
| test("fieldTypeSuccess") { | ||
| val (prog, assertNode) = generateFieldTypeTest(true) | ||
| val res = verifier.verify(prog) | ||
| assert(res == Success) | ||
| } | ||
|
|
||
| test("fieldTypeFail") { | ||
| val (prog, assertNode) = generateFieldTypeTest(false) | ||
| val res = verifier.verify(prog) | ||
| assert(res match { | ||
| case Failure(Seq(AssertFailed(a, _, _))) if a == assertNode => true | ||
| case _ => false | ||
| }) | ||
| } | ||
|
|
||
| test("bvOpSuccess") { | ||
| val (prog, assertNode) = generateBvOpTest(true) | ||
| val res = verifier.verify(prog) | ||
| assert(res == Success) | ||
| } | ||
|
|
||
| test("bvOpFail") { | ||
| val (prog, assertNode) = generateBvOpTest(false) | ||
| val res = verifier.verify(prog) | ||
| assert(res match { | ||
| case Failure(Seq(AssertFailed(a, _, _))) if a == assertNode => true | ||
| case _ => false | ||
| }) | ||
| } | ||
|
|
||
| test("floatOpSuccess") { | ||
| val (prog, assertNode) = generateFloatOpTest(true) | ||
| val res = verifier.verify(prog) | ||
| assert(res == Success) | ||
| } | ||
|
|
||
| test("floatOpFail") { | ||
| val (prog, assertNode) = generateFloatOpTest(false) | ||
| val res = verifier.verify(prog) | ||
| assert(res match { | ||
| case Failure(Seq(AssertFailed(a, _, _))) if a == assertNode => true | ||
| case _ => false | ||
| }) | ||
| } | ||
|
|
||
| test("floatOpFunctionSuccess") { | ||
| val (prog, fun, exp) = generateFloatOpFunctionTest(true) | ||
| val res = verifier.verify(prog) | ||
| assert(res == Success) | ||
| } | ||
|
|
||
| test("floatOpFunctionFail") { | ||
| val (prog, fun, exp) = generateFloatOpFunctionTest(false) | ||
| val res = verifier.verify(prog) | ||
| assert(res match { | ||
| case Failure(Seq(PostconditionViolated(e, f, _, _))) if e == exp && fun == f => true | ||
| case _ => false | ||
| }) | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not overly thrilled about including the Boogie type name as well. How cumbersome would it be if it were left to Carbon (or any other Boogie-based backend) to translate from SMT name to Boogie name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boogie just has support specifically for bitvector and float types, so it cannot work with an arbitrary SMT type. So one could do what you suggested, but one would have to look for these two specific kinds of types and then just fail on anything other than those specific types, that would work.
We could also just rename it from SMTType to BackendType or something like that, I agree that it's quite weird that something called SMT type contains something Boogie-related and is pretty-printed using its Boogie name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we allow arbitrary SMT types (or "backend types"), it either way seems unavoidable that backend verifiers would have to perform consistency checks to reject programs with backend types they don't support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree.