Skip to content

Commit fc8a8c9

Browse files
authored
Merge pull request #692 from viperproject/meilers_conditional_flyweight
Conditional flyweight pattern for terms
2 parents fe9222f + 28481ad commit fc8a8c9

30 files changed

Lines changed: 1125 additions & 699 deletions

src/main/scala/Config.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") {
568568
proverArgs.flatMap(args => proverTimeoutArg findFirstMatchIn args map(_.group(1).toInt))}
569569
.getOrElse(0)
570570

571+
lazy val useFlyweight: Boolean = prover() == "Z3-API"
572+
571573
val maxHeuristicsDepth: ScallopOption[Int] = opt[Int]("maxHeuristicsDepth",
572574
descr = "Maximal number of nested heuristics applications (default: 3)",
573575
default = Some(3),

src/main/scala/decider/Decider.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>
120120
private def getProver(prover: String): Prover = prover match {
121121
case Z3ProverStdIO.name => new Z3ProverStdIO(uniqueId, termConverter, identifierFactory, reporter)
122122
case Cvc5ProverStdIO.name => new Cvc5ProverStdIO(uniqueId, termConverter, identifierFactory, reporter)
123-
case Z3ProverAPI.name => new Z3ProverAPI(uniqueId, new TermToZ3APIConverter(), identifierFactory, reporter)
123+
case Z3ProverAPI.name => new Z3ProverAPI(uniqueId, new TermToZ3APIConverter(), identifierFactory, reporter, triggerGenerator)
124124
case prover =>
125125
val msg1 = s"Unknown prover '$prover' provided. Defaulting to ${Z3ProverStdIO.name}."
126126
logger warn msg1
@@ -273,10 +273,10 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>
273273
}
274274

275275
private def isKnownToBeTrue(t: Term) = t match {
276-
case True() => true
276+
case True => true
277277
// case eq: BuiltinEquals => eq.p0 == eq.p1 /* WARNING: Blocking trivial equalities might hinder axiom triggering. */
278278
case _ if pcs.assumptions contains t => true
279-
case q: Quantification if q.body == True() => true
279+
case q: Quantification if q.body == True => true
280280
case _ => false
281281
}
282282

@@ -382,7 +382,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>
382382

383383
def statistics(): Map[String, String] = prover.statistics()
384384

385-
override def generateModel(): Unit = proverAssert(False(), None)
385+
override def generateModel(): Unit = proverAssert(False, None)
386386

387387
override def getModel(): Model = prover.getModel()
388388

src/main/scala/decider/PathConditions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private trait LayeredPathConditionStackLike {
143143
protected def conditionalized(layers: Stack[PathConditionStackLayer]): Seq[Term] = {
144144
var unconditionalTerms = Vector.empty[Term]
145145
var conditionalTerms = Vector.empty[Term]
146-
var implicationLHS: Term = True()
146+
var implicationLHS: Term = True
147147

148148
for (layer <- layers.reverseIterator) {
149149
unconditionalTerms ++= layer.globalAssumptions
@@ -182,7 +182,7 @@ private trait LayeredPathConditionStackLike {
182182
Quantification(
183183
quantifier,
184184
qvars,
185-
Implies(layer.branchCondition.getOrElse(True()), And(layer.nonGlobalAssumptions -- ignores)),
185+
Implies(layer.branchCondition.getOrElse(True), And(layer.nonGlobalAssumptions -- ignores)),
186186
triggers,
187187
name,
188188
isGlobal)

src/main/scala/decider/TermToSMTLib2Converter.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ class TermToSMTLib2Converter
197197

198198
/* Permissions */
199199

200-
case FullPerm() => "$Perm.Write"
201-
case NoPerm() => "$Perm.No"
200+
case FullPerm => "$Perm.Write"
201+
case NoPerm => "$Perm.No"
202202
case FractionPermLiteral(r) => renderBinaryOp("/", renderAsReal(IntLiteral(r.numerator)), renderAsReal(IntLiteral(r.denominator)))
203203
case FractionPerm(n, d) => renderBinaryOp("/", renderAsReal(n), renderAsReal(d))
204204
case PermLess(t0, t1) => renderBinaryOp("<", render(t0), render(t1))
@@ -370,9 +370,9 @@ class TermToSMTLib2Converter
370370
else parens(text("- 0") <+> value(-n))
371371

372372
case Unit => "$Snap.unit"
373-
case True() => "true"
374-
case False() => "false"
375-
case Null() => "$Ref.null"
373+
case True => "true"
374+
case False => "false"
375+
case Null => "$Ref.null"
376376
case _: SeqNil => renderApp("Seq_empty", Seq(), literal.sort)
377377
case _: EmptySet => renderApp("Set_empty", Seq(), literal.sort)
378378
case _: EmptyMultiset => renderApp("Multiset_empty", Seq(), literal.sort)

src/main/scala/decider/TermToZ3APIConverter.scala

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class TermToZ3APIConverter
3030

3131
val sortCache = mutable.HashMap[Sort, Z3Sort]()
3232
val funcDeclCache = mutable.HashMap[(String, Seq[Sort], Sort), Z3FuncDecl]()
33+
val termCache = mutable.HashMap[Term, Z3Expr]()
3334

3435
def convert(s: Sort): Z3Sort = convertSort(s)
3536

@@ -203,6 +204,9 @@ class TermToZ3APIConverter
203204

204205

205206
def convertTerm(term: Term): Z3Expr = {
207+
val cached = termCache.get(term)
208+
if (cached.isDefined)
209+
return cached.get
206210
val res = term match {
207211
case l: Literal => {
208212
l match {
@@ -212,9 +216,9 @@ class TermToZ3APIConverter
212216
else
213217
ctx.mkUnaryMinus(ctx.mkInt((-n).toString()))
214218
}
215-
case True() => ctx.mkTrue()
216-
case False() => ctx.mkFalse()
217-
case Null() => ctx.mkConst("$Ref.null", ctx.mkUninterpretedSort("$Ref"))
219+
case True => ctx.mkTrue()
220+
case False => ctx.mkFalse()
221+
case Null => ctx.mkConst("$Ref.null", ctx.mkUninterpretedSort("$Ref"))
218222
case Unit => ctx.mkConst(getUnitConstructor)
219223
case _: SeqNil => createApp("Seq_empty", Seq(), l.sort)
220224
case _: EmptySet => createApp("Set_empty", Seq(), l.sort)
@@ -254,11 +258,13 @@ class TermToZ3APIConverter
254258
} else{
255259
val qvarExprs = vars.map(v => convert(v)).toArray
256260
val nonEmptyTriggers = triggers.filter(_.p.nonEmpty)
257-
val patterns = if (nonEmptyTriggers.nonEmpty)
258-
// Simplify trigger terms; Z3 does this automatically when used via stdio, and it sometimes makes
259-
// triggers valid that would otherwise be rejected.
260-
nonEmptyTriggers.map(t => ctx.mkPattern(t.p.map(trm => convertTerm(trm).simplify()): _*)).toArray
261-
else null
261+
val patterns = if (nonEmptyTriggers.nonEmpty) {
262+
// ME: Maybe we should simplify trigger terms here? There is some evidence that Z3 does this
263+
// automatically when used via stdio, and it sometimes makes triggers valid that would otherwise be
264+
// rejected. On the other hand, it's not at all obvious that simplification does not change the shape
265+
// of a trigger term, which would not be what we want.
266+
nonEmptyTriggers.map(t => ctx.mkPattern(t.p.map(trm => convertTerm(trm)): _*)).toArray
267+
} else null
262268
val weightValue = weight.getOrElse(1)
263269
if (quant == Forall) {
264270
ctx.mkForall(qvarExprs, convertTerm(body), weightValue, patterns, null, ctx.mkSymbol(name), null)
@@ -310,8 +316,8 @@ class TermToZ3APIConverter
310316
/* Permissions */
311317

312318

313-
case FullPerm() => ctx.mkReal(1)
314-
case NoPerm() => ctx.mkReal(0)
319+
case FullPerm => ctx.mkReal(1)
320+
case NoPerm => ctx.mkReal(0)
315321
case FractionPermLiteral(r) => ctx.mkDiv(convertToReal(IntLiteral(r.numerator)), convertToReal(IntLiteral(r.denominator)))
316322
case FractionPerm(n, d) => ctx.mkDiv(convertToReal(n), convertToReal(d))
317323
case PermLess(t0, t1) => ctx.mkLt(convertTerm(t0).asInstanceOf[ArithExpr], convertTerm(t1).asInstanceOf[ArithExpr])
@@ -441,6 +447,7 @@ class TermToZ3APIConverter
441447
| _: Quantification =>
442448
sys.error(s"Unexpected term $term cannot be translated to SMTLib code")
443449
}
450+
termCache.put(term, res)
444451
res
445452
}
446453

@@ -516,6 +523,7 @@ class TermToZ3APIConverter
516523
macros.clear()
517524
funcDeclCache.clear()
518525
sortCache.clear()
526+
termCache.clear()
519527
unitConstructor = null
520528
combineConstructor = null
521529
firstFunc = null

src/main/scala/decider/Z3ProverAPI.scala

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ package viper.silicon.decider
99
import com.typesafe.scalalogging.LazyLogging
1010
import viper.silicon.common.config.Version
1111
import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat}
12-
import viper.silicon.state.IdentifierFactory
13-
import viper.silicon.state.terms.{App, Decl, Fun, FunctionDecl, Implies, MacroDecl, Sort, SortDecl, SortWrapperDecl, Term, sorts}
12+
import viper.silicon.state.{IdentifierFactory, State}
13+
import viper.silicon.state.terms.{App, Decl, Fun, FunctionDecl, Implies, Ite, MacroDecl, Quantification, Sort, SortDecl, SortWrapperDecl, Term, Trigger, TriggerGenerator, sorts}
1414
import viper.silicon.{Config, Map}
1515
import viper.silicon.verifier.Verifier
1616
import viper.silver.reporter.{InternalWarningMessage, Reporter}
1717
import viper.silver.verifier.{MapEntry, ModelEntry, ModelParser, ValueEntry, DefaultDependency => SilDefaultDependency, Model => ViperModel}
18+
1819
import java.io.PrintWriter
1920
import java.nio.file.Path
20-
2121
import scala.collection.mutable
2222
import com.microsoft.z3._
23+
import com.microsoft.z3.enumerations.Z3_param_kind
24+
import viper.silicon.decider.Z3ProverAPI.oldVersionOnlyParams
2325
import viper.silicon.reporting.ExternalToolError
2426

2527
import scala.jdk.CollectionConverters.MapHasAsJava
@@ -28,8 +30,8 @@ import scala.util.Random
2830

2931
object Z3ProverAPI {
3032
val name = "Z3-API"
31-
val minVersion = Version("4.8.6.0")
32-
val maxVersion = Some(Version("4.8.7.0")) /* X.Y.Z if that is the *last supported* version */
33+
val minVersion = Version("4.8.7.0")
34+
val maxVersion = Some(Version("4.12.1.0")) /* X.Y.Z if that is the *last supported* version */
3335

3436
// these are not actually used, but since there is a lot of code that expects command line parameters and a
3537
// config file, we just supply this information here (whose contents will then be ignored)
@@ -46,34 +48,31 @@ object Z3ProverAPI {
4648
val initialOptions = Map("auto_config" -> "false", "type_check" -> "true")
4749
val boolParams = Map(
4850
"smt.delay_units" -> true,
49-
"delay_units" -> true,
5051
"smt.mbqi" -> false,
51-
"mbqi" -> false,
5252
//"pp.bv_literals" -> false, // This is part of z3config.smt2 but Z3 won't accept it via API.
5353
"model.v2" -> true
5454
)
5555
val intParams = Map(
5656
"smt.case_split" -> 3,
57-
"case_split" -> 3,
5857
"smt.qi.max_multi_patterns" -> 1000,
59-
"qi.max_multi_patterns" -> 1000,
6058
"smt.arith.solver" -> 2,
61-
"arith.solver" -> 2
6259
)
6360
val stringParams: Map[String, String] = Map(
6461
// currently none
6562
)
6663
val doubleParams = Map(
6764
"smt.qi.eager_threshold" -> 100.0,
68-
"qi.eager_threshold" -> 100.0,
6965
)
66+
val allParams = boolParams ++ intParams ++ stringParams ++ doubleParams
67+
val oldVersionOnlyParams = Set("smt.arith.solver")
7068
}
7169

7270

7371
class Z3ProverAPI(uniqueId: String,
7472
termConverter: TermToZ3APIConverter,
7573
identifierFactory: IdentifierFactory,
76-
reporter: Reporter)
74+
reporter: Reporter,
75+
triggerGenerator: TriggerGenerator)
7776
extends Prover
7877
with LazyLogging
7978
{
@@ -114,19 +113,59 @@ class Z3ProverAPI(uniqueId: String,
114113
lastTimeout = -1
115114
ctx = new Context(Z3ProverAPI.initialOptions.asJava)
116115
val params = ctx.mkParams()
116+
117+
// When setting parameters via API, we have to remove the smt. prefix
118+
def removeSmtPrefix(s: String) = {
119+
if (s.startsWith("smt."))
120+
s.substring(4)
121+
else
122+
s
123+
}
124+
125+
val useOldVersionParams = version() <= Version("4.8.7.0")
117126
Z3ProverAPI.boolParams.foreach{
118-
case (k, v) => params.add(k, v)
127+
case (k, v) =>
128+
if (useOldVersionParams || !oldVersionOnlyParams.contains(k))
129+
params.add(removeSmtPrefix(k), v)
119130
}
120131
Z3ProverAPI.intParams.foreach{
121-
case (k, v) => params.add(k, v)
132+
case (k, v) =>
133+
if (useOldVersionParams || !oldVersionOnlyParams.contains(k))
134+
params.add(removeSmtPrefix(k), v)
122135
}
123136
Z3ProverAPI.doubleParams.foreach{
124-
case (k, v) => params.add(k, v)
137+
case (k, v) =>
138+
if (useOldVersionParams || !oldVersionOnlyParams.contains(k))
139+
params.add(removeSmtPrefix(k), v)
125140
}
126141
Z3ProverAPI.stringParams.foreach{
127-
case (k, v) => params.add(k, v)
142+
case (k, v) =>
143+
if (useOldVersionParams || !oldVersionOnlyParams.contains(k))
144+
params.add(removeSmtPrefix(k), v)
128145
}
146+
val userProvidedArgs = Verifier.config.proverConfigArgs
129147
prover = ctx.mkSolver()
148+
val descrs = prover.getParameterDescriptions
149+
for ((origKey, vl) <- userProvidedArgs) {
150+
val key = if (origKey.startsWith("smt."))
151+
origKey.substring(4)
152+
else
153+
origKey
154+
val keySymbol = ctx.mkSymbol(key)
155+
val param_kind = descrs.getKind(keySymbol)
156+
param_kind match {
157+
case Z3_param_kind.Z3_PK_BOOL =>
158+
params.add(key, vl.toBoolean)
159+
case Z3_param_kind.Z3_PK_UINT =>
160+
params.add(key, vl.toInt)
161+
case Z3_param_kind.Z3_PK_DOUBLE =>
162+
params.add(key, vl.toDouble)
163+
case Z3_param_kind.Z3_PK_STRING =>
164+
params.add(key, vl)
165+
case _ =>
166+
reporter.report(InternalWarningMessage("Z3 error: unknown parameter" + key))
167+
}
168+
}
130169
prover.setParameters(params)
131170
termConverter.start()
132171
termConverter.ctx = ctx
@@ -212,7 +251,21 @@ class Z3ProverAPI(uniqueId: String,
212251
else
213252
preambleAssumes.add(termConverter.convert(term).asInstanceOf[BoolExpr])
214253
} catch {
215-
case e: Z3Exception => reporter.report(InternalWarningMessage("Z3 error: " + e.getMessage))
254+
case e: Z3Exception =>
255+
// The only reason we get an exception here is that we've tried to assume a quantifier with an invalid trigger.
256+
// When used via API, Z3 completely discards assumptions that contain invalid triggers (whereas it just ignores
257+
// the invalid trigger when used via stdio). Thus, to make sure our assumption is not discarded, we manually
258+
// walk through all quantifiers and remove invalid terms inside the trigger.
259+
triggerGenerator.setCustomIsForbiddenInTrigger(triggerGenerator.advancedIsForbiddenInTrigger)
260+
val cleanTerm = term.transform{
261+
case q@Quantification(_, _, _, triggers, _, _, _) if triggers.nonEmpty =>
262+
val goodTriggers = triggers.filterNot(trig => trig.p.exists(ptrn => ptrn.shallowCollect{
263+
case t => triggerGenerator.isForbiddenInTrigger(t)
264+
}.nonEmpty))
265+
q.copy(triggers = goodTriggers)
266+
}()
267+
prover.add(termConverter.convert(cleanTerm).asInstanceOf[BoolExpr])
268+
reporter.report(InternalWarningMessage("Z3 error: " + e.getMessage))
216269
}
217270
}
218271

@@ -316,7 +369,13 @@ class Z3ProverAPI(uniqueId: String,
316369
if (!preamblePhaseOver) {
317370
preamblePhaseOver = true
318371

319-
val merged = emittedPreambleString.mkString("\n")
372+
// Setting all options again , since otherwise some of them seem to get lost.
373+
val standardOptionPrefix = Seq("(set-option :auto_config false)", "(set-option :type_check true)") ++
374+
Z3ProverAPI.allParams.map(bp => s"(set-option :${bp._1} ${bp._2})")
375+
376+
val customOptionPrefix = Verifier.config.proverConfigArgs.map(a => s"(set-option :${a._1} ${a._2})")
377+
378+
val merged = (standardOptionPrefix ++ customOptionPrefix ++ emittedPreambleString).mkString("\n")
320379
val parsed = ctx.parseSMTLIB2String(merged, emittedSortSymbols.toArray, emittedSorts.toArray, emittedFuncSymbols.toArray, emittedFuncs.toArray)
321380
prover.add(parsed: _*)
322381
prover.add(preambleAssumes.toSeq : _*)

src/main/scala/reporting/Converter.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ object Converter {
151151
lazy val symbolConverter: SymbolConverter = new DefaultSymbolConverter
152152
//some tokens used for naming model entries in a more maintainable way
153153
lazy val snapUnitId: String = termconverter.convert(Unit)
154-
lazy val nullRefId: String = termconverter.convert(Null())
154+
lazy val nullRefId: String = termconverter.convert(Null)
155155

156156
def getFunctionValue(model: Model,
157157
fname: String,
@@ -254,7 +254,7 @@ object Converter {
254254
case Unit => UnprocessedModelEntry(ConstantEntry(snapUnitId))
255255
case IntLiteral(x) => LitIntEntry(x)
256256
case t: BooleanLiteral => LitBoolEntry(t.value)
257-
case Null() => VarEntry(model.entries(nullRefId).toString, sorts.Ref)
257+
case Null => VarEntry(model.entries(nullRefId).toString, sorts.Ref)
258258
case Var(_, sort) =>
259259
val key: String = term.toString
260260
val entry: Option[ModelEntry] = model.entries.get(key)
@@ -416,8 +416,8 @@ object Converter {
416416
case _ => None
417417
}
418418
case App(_, _) => None
419-
case NoPerm() => Some(Rational.zero)
420-
case FullPerm() => Some(Rational.one)
419+
case NoPerm => Some(Rational.zero)
420+
case FullPerm => Some(Rational.one)
421421
case FractionPermLiteral(r) => Some(r)
422422
case _: FractionPerm => None
423423
case IsValidPermVar(_) => None

src/main/scala/reporting/Formatters.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class DefaultStateFormatter extends StateFormatter {
5858
case c: BuiltinEquals if c.p0.isInstanceOf[Combine]
5959
|| c.p1.isInstanceOf[Combine]
6060
=> true
61-
case Not(BuiltinEquals(_, Null())) => true
61+
case Not(BuiltinEquals(_, Null)) => true
6262
case _ => false
6363
}.mkString("(", ", ", ")")
6464
}
@@ -92,7 +92,7 @@ class DefaultStateFormatter extends StateFormatter {
9292
val filteredPcs = pcs.filterNot {
9393
case c: BuiltinEquals if c.p0.isInstanceOf[Combine]
9494
|| c.p1.isInstanceOf[Combine] => true
95-
case Not(BuiltinEquals(_, Null())) => true
95+
case Not(BuiltinEquals(_, Null)) => true
9696
case _ => false
9797
}
9898
if (filteredPcs.isEmpty) "[]" else filteredPcs.mkString("[\"", "\",\"", "\"]")

0 commit comments

Comments
 (0)