//----------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All Rights Reserved. // //----------------------------------------------------------------------------- using System; using System.Text; using System.IO; using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using Microsoft.Basetypes; using Microsoft.Boogie.VCExprAST; // Method to turn VCExprs into strings that can be fed into SMT // solvers. This is currently quite similar to the // SimplifyLikeLineariser (but the code is independent) namespace Microsoft.Boogie.SMTLib { // Options for the linearisation public class LineariserOptions { public static LineariserOptions Default = new LineariserOptions(); public bool LabelsBelowQuantifiers = false; } //////////////////////////////////////////////////////////////////////////////////////// // Lineariser for expressions. The result (bool) is currently not used for anything public class SMTLibExprLineariser : IVCExprVisitor { public static string ToString(VCExpr e, UniqueNamer namer, SMTLibProverOptions opts, ISet namedAssumes = null, IList optReqs = null, ISet tryAssumes = null) { Contract.Requires(e != null); Contract.Requires(namer != null); Contract.Ensures(Contract.Result() != null); StringWriter sw = new StringWriter(); SMTLibExprLineariser lin = new SMTLibExprLineariser(sw, namer, opts, namedAssumes, optReqs); Contract.Assert(lin != null); lin.Linearise(e, LineariserOptions.Default); return cce.NonNull(sw.ToString()); } //////////////////////////////////////////////////////////////////////////////////////// private readonly TextWriter wr; [ContractInvariantMethod] void ObjectInvariant() { Contract.Invariant(wr != null); Contract.Invariant(Namer != null); } private SMTLibOpLineariser OpLinObject = null; private IVCExprOpVisitor/*!>!*/ OpLineariser { get { Contract.Ensures(Contract.Result>() != null); if (OpLinObject == null) OpLinObject = new SMTLibOpLineariser(this, wr); return OpLinObject; } } internal readonly UniqueNamer Namer; internal int UnderQuantifier = 0; internal readonly SMTLibProverOptions ProverOptions; readonly IList OptimizationRequests; readonly ISet NamedAssumes; public SMTLibExprLineariser(TextWriter wr, UniqueNamer namer, SMTLibProverOptions opts, ISet namedAssumes = null, IList optReqs = null) { Contract.Requires(wr != null); Contract.Requires(namer != null); this.wr = wr; this.Namer = namer; this.ProverOptions = opts; this.OptimizationRequests = optReqs; this.NamedAssumes = namedAssumes; } public void Linearise(VCExpr expr, LineariserOptions options) { Contract.Requires(expr != null); Contract.Requires(options != null); expr.Accept(this, options); } ///////////////////////////////////////////////////////////////////////////////////// private static void TypeToStringHelper(Type t, StringBuilder sb) { Contract.Requires(t != null); TypeSynonymAnnotation syn = t as TypeSynonymAnnotation; if (syn != null) { TypeToStringHelper(syn.ExpandedType, sb); } else { if (t.IsMap && CommandLineOptions.Clo.UseArrayTheory) { MapType m = t.AsMap; // Contract.Assert(m.MapArity == 1); sb.Append("(Array "); foreach (Type tp in m.Arguments) sb.Append(TypeToString(tp)).Append(" "); sb.Append(TypeToString(m.Result)).Append(")"); } else if (t.IsMap) { MapType m = t.AsMap; sb.Append('['); for (int i = 0; i < m.MapArity; ++i) { if (i != 0) sb.Append(','); TypeToStringHelper(m.Arguments[i], sb); } sb.Append(']'); TypeToStringHelper(m.Result, sb); } else if (t.IsBool || t.IsInt || t.IsReal || t.IsBv) { sb.Append(TypeToString(t)); } else { System.IO.StringWriter buffer = new System.IO.StringWriter(); using (TokenTextWriter stream = new TokenTextWriter("", buffer, /*setTokens=*/false, /*pretty=*/false)) { t.Emit(stream); } sb.Append(buffer.ToString()); } } } public static string TypeToString(Type t) { Contract.Requires(t != null); Contract.Ensures(Contract.Result() != null); if (t.IsBool) return "Bool"; else if (t.IsInt) return "Int"; else if (t.IsReal) return "Real"; else if (t.IsBv) { return "(_ BitVec " + t.BvBits + ")"; } else { StringBuilder sb = new StringBuilder(); TypeToStringHelper(t, sb); var s = sb.ToString(); if (s[0] == '(') return s; else return SMTLibNamer.QuoteId("T@" + s); } } public static string ExtractBuiltin(Function f) { Contract.Requires(f != null); string retVal = null; retVal = f.FindStringAttribute("bvbuiltin"); // It used to be "sign_extend 12" in Simplify, and is "(_ sign_extend 12)" with SMT if (retVal != null && (retVal.StartsWith("sign_extend ") || retVal.StartsWith("zero_extend "))) return "(_ " + retVal + ")"; if (retVal == null) { retVal = f.FindStringAttribute("builtin"); } if (retVal != null && !CommandLineOptions.Clo.UseArrayTheory && SMTLibOpLineariser.ArrayOps.Contains(retVal)) { retVal = null; } return retVal; } ///////////////////////////////////////////////////////////////////////////////////// public bool Visit(VCExprLiteral node, LineariserOptions options) { if (node == VCExpressionGenerator.True) wr.Write("true"); else if (node == VCExpressionGenerator.False) wr.Write("false"); else if (node is VCExprIntLit) { BigNum lit = ((VCExprIntLit)node).Val; if (lit.IsNegative) // In SMT2 "-42" is an identifier (SMT2, Sect. 3.2 "Symbols") wr.Write("(- 0 {0})", lit.Abs); else wr.Write(lit); } else if (node is VCExprRealLit) { BigDec lit = ((VCExprRealLit)node).Val; if (lit.IsNegative) // In SMT2 "-42" is an identifier (SMT2, Sect. 3.2 "Symbols") wr.Write("(- 0.0 {0})", lit.Abs.ToDecimalString()); else wr.Write(lit.ToDecimalString()); } else { Contract.Assert(false); throw new cce.UnreachableException(); } return true; } ///////////////////////////////////////////////////////////////////////////////////// public bool Visit(VCExprNAry node, LineariserOptions options) { VCExprOp op = node.Op; Contract.Assert(op != null); var booleanOps = new HashSet(); booleanOps.Add(VCExpressionGenerator.NotOp); booleanOps.Add(VCExpressionGenerator.ImpliesOp); booleanOps.Add(VCExpressionGenerator.AndOp); booleanOps.Add(VCExpressionGenerator.OrOp); if (booleanOps.Contains(op)) { Stack exprs = new Stack(); exprs.Push(node); while (exprs.Count > 0) { VCExpr expr = exprs.Pop(); if (expr == null) { wr.Write(")"); continue; } wr.Write(" "); VCExprNAry naryExpr = expr as VCExprNAry; if (naryExpr == null || !booleanOps.Contains(naryExpr.Op)) { Linearise(expr, options); continue; } else if (naryExpr.Op.Equals(VCExpressionGenerator.NotOp)) { wr.Write("(not"); } else if (naryExpr.Op.Equals(VCExpressionGenerator.ImpliesOp)) { wr.Write("(=>"); } else if (naryExpr.Op.Equals(VCExpressionGenerator.AndOp)) { wr.Write("(and"); } else { System.Diagnostics.Debug.Assert(naryExpr.Op.Equals(VCExpressionGenerator.OrOp)); wr.Write("(or"); } exprs.Push(null); for (int i = naryExpr.Arity - 1; i >= 0; i--) { exprs.Push(naryExpr[i]); } } return true; } if (OptimizationRequests != null && (node.Op.Equals(VCExpressionGenerator.MinimizeOp) || node.Op.Equals(VCExpressionGenerator.MaximizeOp))) { string optOp = node.Op.Equals(VCExpressionGenerator.MinimizeOp) ? "minimize" : "maximize"; OptimizationRequests.Add(string.Format("({0} {1})", optOp, ToString(node[0], Namer, ProverOptions, NamedAssumes))); Linearise(node[1], options); return true; } if (node.Op is VCExprSoftOp) { Linearise(node[1], options); return true; } if (node.Op.Equals(VCExpressionGenerator.NamedAssumeOp)) { var exprVar = node[0] as VCExprVar; NamedAssumes.Add(exprVar); Linearise(node[1], options); return true; } return node.Accept(OpLineariser, options); } ///////////////////////////////////////////////////////////////////////////////////// public bool Visit(VCExprVar node, LineariserOptions options) { wr.Write(Namer.GetQuotedName(node, node.Name)); return true; } ///////////////////////////////////////////////////////////////////////////////////// public bool Visit(VCExprQuantifier node, LineariserOptions options) { Contract.Assert(node.TypeParameters.Count == 0); UnderQuantifier++; Namer.PushScope(); try { string kind = node.Quan == Quantifier.ALL ? "forall" : "exists"; wr.Write("({0} (", kind); for (int i = 0; i < node.BoundVars.Count; i++) { VCExprVar var = node.BoundVars[i]; Contract.Assert(var != null); string printedName = Namer.GetQuotedLocalName(var, var.Name); Contract.Assert(printedName != null); wr.Write("({0} {1}) ", printedName, TypeToString(var.Type)); } wr.Write(") "); VCQuantifierInfos infos = node.Infos; var weight = QKeyValue.FindIntAttribute(infos.attributes, "weight", 1); if (!ProverOptions.UseWeights) weight = 1; var hasAttrs = node.Triggers.Count > 0 || infos.qid != null || weight != 1 || infos.uniqueId != -1; if (hasAttrs) wr.Write("(! "); Linearise(node.Body, options); if (hasAttrs) { wr.Write("\n"); if (infos.qid != null) wr.Write(" :qid {0}\n", SMTLibNamer.QuoteId(infos.qid)); if (weight != 1) wr.Write(" :weight {0}\n", weight); if (infos.uniqueId != -1) wr.Write(" :skolemid |{0}|\n", infos.uniqueId); WriteTriggers(node.Triggers, options); wr.Write(")"); } wr.Write(")"); return true; } finally { UnderQuantifier--; Namer.PopScope(); } } private void WriteTriggers(List!*/> triggers, LineariserOptions options) { Contract.Requires(options != null); Contract.Requires(triggers != null); // first, count how many neg/pos triggers there are int negTriggers = 0; int posTriggers = 0; foreach (VCTrigger vcTrig in triggers) { Contract.Assert(vcTrig != null); if (vcTrig.Pos) { posTriggers++; } else { negTriggers++; } } if (posTriggers > 0) { foreach (VCTrigger vcTrig in triggers) { Contract.Assert(vcTrig != null); if (vcTrig.Pos) { wr.Write(" :pattern ("); foreach (VCExpr e in vcTrig.Exprs) { Contract.Assert(e != null); wr.Write(" "); var subPat = e; var nary = e as VCExprNAry; if (nary != null && (nary.Op == VCExpressionGenerator.NeqOp || nary.Op == VCExpressionGenerator.EqOp)) { if (nary[0] is VCExprLiteral) subPat = nary[1]; else if (nary[1] is VCExprLiteral) subPat = nary[0]; } Linearise(subPat, options); } wr.Write(")\n"); } } } else if (negTriggers > 0) { // if also positive triggers are given, the SMT solver (at least Z3) // will ignore the negative patterns and output a warning. Therefore // we never specify both negative and positive triggers foreach (VCTrigger vcTrig in triggers) { Contract.Assert(vcTrig != null); if (!vcTrig.Pos) { wr.Write(" :no-pattern "); Contract.Assert(vcTrig.Exprs.Count == 1); Linearise(vcTrig.Exprs[0], options); wr.Write("\n"); } } } } ///////////////////////////////////////////////////////////////////////////////////// public bool Visit(VCExprLet node, LineariserOptions options) { Namer.PushScope(); try { foreach (VCExprLetBinding b in node) { wr.Write("(let ("); Contract.Assert(b != null); wr.Write("({0} ", Namer.GetQuotedName(b.V, b.V.Name)); Linearise(b.E, options); wr.Write("))\n"); } Linearise(node.Body, options); foreach (VCExprLetBinding b in node) wr.Write(")"); return true; } finally { Namer.PopScope(); } } ///////////////////////////////////////////////////////////////////////////////////// // Lineariser for operator terms. The result (bool) is currently not used for anything internal class SMTLibOpLineariser : IVCExprOpVisitor { private readonly SMTLibExprLineariser ExprLineariser; private readonly TextWriter wr; [ContractInvariantMethod] void ObjectInvariant() { Contract.Invariant(wr != null); Contract.Invariant(ExprLineariser != null); } public SMTLibOpLineariser(SMTLibExprLineariser ExprLineariser, TextWriter wr) { Contract.Requires(ExprLineariser != null); Contract.Requires(wr != null); this.ExprLineariser = ExprLineariser; this.wr = wr; } /////////////////////////////////////////////////////////////////////////////////// private void WriteApplication(string opName, IEnumerable/*!>!*/ args, LineariserOptions options) { Contract.Requires(cce.NonNullElements(args)); Contract.Requires(options != null); Contract.Assert(opName != null); bool hasArgs = false; foreach (VCExpr e in args) { Contract.Assert(e != null); if (!hasArgs) wr.Write("({0}", opName); wr.Write(" "); ExprLineariser.Linearise(e, options); hasArgs = true; } if (hasArgs) wr.Write(")"); else wr.Write("{0}", opName); } /////////////////////////////////////////////////////////////////////////////////// public bool VisitNotOp(VCExprNAry node, LineariserOptions options) { WriteApplication("not", node, options); // arguments can be both terms and formulas return true; } private bool PrintEq(VCExprNAry node, LineariserOptions options) { Contract.Requires(node != null); Contract.Requires(options != null); WriteApplication("=", node, options); return true; } public bool VisitEqOp(VCExprNAry node, LineariserOptions options) { return PrintEq(node, options); } public bool VisitNeqOp(VCExprNAry node, LineariserOptions options) { //Contract.Requires(node != null); //Contract.Requires(options != null); wr.Write("(not "); PrintEq(node, options); wr.Write(")"); return true; } public bool VisitAndOp(VCExprNAry node, LineariserOptions options) { WriteApplication("and", node, options); return true; } public bool VisitOrOp(VCExprNAry node, LineariserOptions options) { WriteApplication("or", node, options); return true; } public bool VisitImpliesOp(VCExprNAry node, LineariserOptions options) { WriteApplication("=>", node, options); return true; } public bool VisitIfThenElseOp(VCExprNAry node, LineariserOptions options) { WriteApplication("ite", node, options); return true; } public bool VisitCustomOp(VCExprNAry node, LineariserOptions options) { VCExprCustomOp op = (VCExprCustomOp)node.Op; if (!ExprLineariser.ProverOptions.UseTickleBool && op.Name == "tickleBool") ExprLineariser.Linearise(VCExpressionGenerator.True, options); else WriteApplication(op.Name, node, options); return true; } public bool VisitDistinctOp(VCExprNAry node, LineariserOptions options) { //Contract.Requires(node != null); //Contract.Requires(options != null); if (node.Length < 2) { ExprLineariser.Linearise(VCExpressionGenerator.True, options); } else { var groupings = node.GroupBy(e => e.Type).Where(g => g.Count() > 1).ToArray(); if (groupings.Length == 0) { ExprLineariser.Linearise(VCExpressionGenerator.True, options); } else { if (groupings.Length > 1) wr.Write("(and "); foreach (var g in groupings) { wr.Write("(distinct"); foreach (VCExpr e in g) { Contract.Assert(e != null); wr.Write(" "); ExprLineariser.Linearise(e, options); } wr.Write(")"); } if (groupings.Length > 1) wr.Write(")"); wr.Write("\n"); } } return true; } public bool VisitLabelOp(VCExprNAry node, LineariserOptions options) { if (ExprLineariser.UnderQuantifier > 0 && !options.LabelsBelowQuantifiers) { ExprLineariser.Linearise(node[0], options); return true; } var op = (VCExprLabelOp)node.Op; if (CommandLineOptions.Clo.UseLabels) { // Z3 extension //wr.Write("({0} {1} ", op.pos ? "lblpos" : "lblneg", SMTLibNamer.QuoteId(op.label)); wr.Write("(! "); } if(!options.LabelsBelowQuantifiers) wr.Write("({0} {1} ", op.pos ? "and" : "or", SMTLibNamer.QuoteId(SMTLibNamer.LabelVar(op.label))); ExprLineariser.Linearise(node[0], options); if (!options.LabelsBelowQuantifiers) wr.Write(")"); if (CommandLineOptions.Clo.UseLabels) wr.Write(" :{0} {1})", op.pos ? "lblpos" : "lblneg", SMTLibNamer.QuoteId(op.label)); return true; } public bool VisitSelectOp(VCExprNAry node, LineariserOptions options) { var name = SimplifyLikeExprLineariser.SelectOpName(node); name = ExprLineariser.Namer.GetQuotedName(name, name); if (CommandLineOptions.Clo.UseArrayTheory) name = "select"; WriteApplication(name, node, options); return true; } public bool VisitStoreOp(VCExprNAry node, LineariserOptions options) { var name = SimplifyLikeExprLineariser.StoreOpName(node); name = ExprLineariser.Namer.GetQuotedName(name, name); if (CommandLineOptions.Clo.UseArrayTheory) name = "store"; WriteApplication(name, node, options); return true; } static char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public bool VisitBvOp(VCExprNAry node, LineariserOptions options) { var lit = (VCExprIntLit)node[0]; var bytes = lit.Val.ToByteArray(); if (node.Type.BvBits % 8 == 0) { wr.Write("#x"); for (var pos = node.Type.BvBits / 8 - 1; pos >= 0; pos--) { var k = pos < bytes.Length ? bytes[pos] : (byte)0; wr.Write(hex[k >> 4]); wr.Write(hex[k & 0xf]); } } else { wr.Write("#b"); for (var pos = node.Type.BvBits - 1; pos >= 0; pos--) { var i = pos >> 3; var k = i < bytes.Length ? bytes[i] : (byte)0; wr.Write((k & (1 << (pos & 7))) == 0 ? '0' : '1'); } } return true; } public bool VisitBvExtractOp(VCExprNAry node, LineariserOptions options) { var op = (VCExprBvExtractOp)node.Op; wr.Write("((_ extract {0} {1}) ", op.End - 1, op.Start); ExprLineariser.Linearise(node[0], options); wr.Write(")"); return true; } public bool VisitBvConcatOp(VCExprNAry node, LineariserOptions options) { WriteApplication("concat", node, options); return true; } public bool VisitAddOp(VCExprNAry node, LineariserOptions options) { WriteApplication("+", node, options); return true; } public bool VisitSubOp(VCExprNAry node, LineariserOptions options) { WriteApplication("-", node, options); return true; } public bool VisitMulOp(VCExprNAry node, LineariserOptions options) { WriteApplication("*", node, options); return true; } public bool VisitDivOp(VCExprNAry node, LineariserOptions options) { WriteApplication("div", node, options); return true; } public bool VisitModOp(VCExprNAry node, LineariserOptions options) { WriteApplication("mod", node, options); return true; } public bool VisitRealDivOp(VCExprNAry node, LineariserOptions options) { WriteApplication("/", node, options); return true; } public bool VisitPowOp(VCExprNAry node, LineariserOptions options) { WriteApplication("real_pow", node, options); return true; } public bool VisitLtOp(VCExprNAry node, LineariserOptions options) { WriteApplication("<", node, options); return true; } public bool VisitLeOp(VCExprNAry node, LineariserOptions options) { WriteApplication("<=", node, options); return true; } public bool VisitGtOp(VCExprNAry node, LineariserOptions options) { WriteApplication(">", node, options); return true; } public bool VisitGeOp(VCExprNAry node, LineariserOptions options) { WriteApplication(">=", node, options); return true; } public bool VisitSubtypeOp(VCExprNAry node, LineariserOptions options) { WriteApplication("UOrdering2", node, options); return true; } public bool VisitSubtype3Op(VCExprNAry node, LineariserOptions options) { WriteApplication("UOrdering3", node, options); return true; } public bool VisitToIntOp(VCExprNAry node, LineariserOptions options) { WriteApplication("to_int", node, options); return true; } public bool VisitToRealOp(VCExprNAry node, LineariserOptions options) { WriteApplication("to_real", node, options); return true; } private string ExtractDatatype(Function func) { if (func is DatatypeSelector) { DatatypeSelector selector = (DatatypeSelector) func; Variable v = selector.constructor.InParams[selector.index]; return ExprLineariser.Namer.GetQuotedName(v, v.Name + "#" + selector.constructor.Name); } else if (func is DatatypeMembership) { DatatypeMembership membership = (DatatypeMembership)func; return ExprLineariser.Namer.GetQuotedName(membership, "is-" + membership.constructor.Name); } else { return null; } } public bool VisitBoogieFunctionOp(VCExprNAry node, LineariserOptions options) { VCExprBoogieFunctionOp op = (VCExprBoogieFunctionOp)node.Op; Contract.Assert(op != null); string printedName; var builtin = ExtractBuiltin(op.Func); var datatype = ExtractDatatype(op.Func); if (builtin != null) { printedName = CheckMapApply(builtin, node); } else if (datatype != null) { printedName = datatype; } else { printedName = ExprLineariser.Namer.GetQuotedName(op.Func, op.Func.Name); } Contract.Assert(printedName != null); WriteApplication(printedName, node, options); return true; } private static Type ResultType(Type type) { MapType mapType = type as MapType; if (mapType != null) { return ResultType(mapType.Result); } else { return type; } } public static HashSet ArrayOps = new HashSet(new string[] { "MapConst", "MapAdd", "MapSub", "MapMul", "MapDiv", "MapMod", "MapEq", "MapIff", "MapGt", "MapGe", "MapLt", "MapLe", "MapOr", "MapAnd", "MapNot", "MapImp", "MapIte" }); private static string CheckMapApply(string name, VCExprNAry node) { if (name == "MapConst") { Type type = node.Type; string s = TypeToString(type); return "(as const " + s + ")"; } else if (name == "MapAdd") { return "(_ map (+ (Int Int) Int))"; } else if (name == "MapSub") { return "(_ map (- (Int Int) Int))"; } else if (name == "MapMul") { return "(_ map (* (Int Int) Int))"; } else if (name == "MapDiv") { return "(_ map (div (Int Int) Int))"; } else if (name == "MapMod") { return "(_ map (mod (Int Int) Int))"; } else if (name == "MapEq") { Type type = ResultType(node[0].Type); string s = TypeToString(type); return "(_ map (= (" + s + " " + s + ") Bool))"; } else if (name == "MapIff") { return "(_ map (= (Bool Bool) Bool))"; } else if (name == "MapGt") { return "(_ map (> (Int Int) Int))"; } else if (name == "MapGe") { return "(_ map (>= (Int Int) Int))"; } else if (name == "MapLt") { return "(_ map (< (Int Int) Int))"; } else if (name == "MapLe") { return "(_ map (<= (Int Int) Int))"; } else if (name == "MapOr") { return "(_ map or)"; } else if (name == "MapAnd") { return "(_ map and)"; } else if (name == "MapNot") { return "(_ map not)"; } else if (name == "MapImp") { return "(_ map =>)"; } else if (name == "MapIte") { Type type = ResultType(node.Type); string s = TypeToString(type); return "(_ map (ite (Bool " + s + " " + s + ") " + s + "))"; } else { return name; } } } } }