Skip to content

Commit

Permalink
Merge branch 'master' into meilers_more_joins
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoeilers authored Oct 3, 2023
2 parents ea2173c + 4cdc6b9 commit 1c3b91e
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 25 deletions.
44 changes: 32 additions & 12 deletions src/main/scala/decider/TermToZ3APIConverter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class TermToZ3APIConverter

val sortCache = mutable.HashMap[Sort, Z3Sort]()
val funcDeclCache = mutable.HashMap[(String, Seq[Sort], Sort), Z3FuncDecl]()
val smtFuncDeclCache = mutable.HashMap[(String, Seq[Sort]), (Z3FuncDecl, Seq[Z3Expr])]()
val termCache = mutable.HashMap[Term, Z3Expr]()

def convert(s: Sort): Z3Sort = convertSort(s)
Expand Down Expand Up @@ -471,19 +472,37 @@ class TermToZ3APIConverter
// workaround: since we cannot create a function application with just the name, we let Z3 parse
// a string that uses the function, take the AST, and get the func decl from there, so that we can
// programmatically create a func app.
val decls = args.zipWithIndex.map{case (a, i) => s"(declare-const workaround${i} ${smtlibConverter.convert(a.sort)})"}.mkString(" ")
val funcAppString = s"(${functionName} ${(0 until args.length).map(i => "workaround" + i).mkString(" ")})"
val assertion = decls + s" (assert (= ${funcAppString} ${funcAppString}))"
val workaround = ctx.parseSMTLIB2String(assertion, null, null, null, null)
val app = workaround(0).getArgs()(0)
val decl = app.getFuncDecl
val actualArgs = if (decl.getArity > args.length){
// the function name we got wasn't just a function name but also contained a first argument.
// this happens with float operations where functionName contains a rounding mode.
app.getArgs.toSeq.slice(0, decl.getArity - args.length) ++ args.map(convertTerm(_))
}else {
args.map(convertTerm(_))

val cacheKey = (functionName, args.map(_.sort))
val (decl, additionalArgs: Seq[Z3Expr]) = if (smtFuncDeclCache.contains(cacheKey)) {
smtFuncDeclCache(cacheKey)
} else {
val declPreamble = "(define-sort $Perm () Real) " // ME: Re-declare the Perm sort.
// ME: The parsing happens in a fresh context that doesn't know any of our current declarations.
// In principle, it might be necessary to re-declare all sorts we're using anywhere. However, I don't see how there
// could be any Z3 internal functions that exist for those custom sorts. For the Real (i.e., Perm) sort, however,
// such functions exist. So we re-declare *only* this sort.
val decls = declPreamble + args.zipWithIndex.map { case (a, i) => s"(declare-const workaround${i} ${smtlibConverter.convert(a.sort)})" }.mkString(" ")
val funcAppString = if (args.nonEmpty)
s"(${functionName} ${(0 until args.length).map(i => "workaround" + i).mkString(" ")})"
else
functionName
val assertion = decls + s" (assert (= ${funcAppString} ${funcAppString}))"
val workaround = ctx.parseSMTLIB2String(assertion, null, null, null, null)
val app = workaround(0).getArgs()(0)
val decl = app.getFuncDecl
val additionalArgs = if (decl.getArity > args.length) {
// the function name we got wasn't just a function name but also contained a first argument.
// this happens with float operations where functionName contains a rounding mode.
app.getArgs.toSeq.slice(0, decl.getArity - args.length)
} else {
Seq()
}
smtFuncDeclCache.put(cacheKey, (decl, additionalArgs))
(decl, additionalArgs)
}

val actualArgs = additionalArgs ++ args.map(convertTerm(_))
ctx.mkApp(decl, actualArgs.toArray : _*)
}

Expand Down Expand Up @@ -522,6 +541,7 @@ class TermToZ3APIConverter
sanitizedNamesCache.clear()
macros.clear()
funcDeclCache.clear()
smtFuncDeclCache.clear()
sortCache.clear()
termCache.clear()
unitConstructor = null
Expand Down
7 changes: 4 additions & 3 deletions src/main/scala/decider/Z3ProverAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.typesafe.scalalogging.LazyLogging
import viper.silicon.common.config.Version
import viper.silicon.interfaces.decider.{Prover, Result, Sat, Unknown, Unsat}
import viper.silicon.state.IdentifierFactory
import viper.silicon.state.terms.{App, Decl, Fun, FunctionDecl, Implies, MacroDecl, Not, Quantification, Sort, SortDecl, SortWrapperDecl, Term, TriggerGenerator, sorts}
import viper.silicon.state.terms.{App, Decl, Fun, FunctionDecl, Implies, MacroDecl, Not, Quantification, Sort, SortDecl, SortWrapperDecl, Term, TriggerGenerator, Var, sorts}
import viper.silicon.{Config, Map}
import viper.silicon.verifier.Verifier
import viper.silver.reporter.{InternalWarningMessage, Reporter}
Expand Down Expand Up @@ -258,11 +258,12 @@ class Z3ProverAPI(uniqueId: String,
triggerGenerator.setCustomIsForbiddenInTrigger(triggerGenerator.advancedIsForbiddenInTrigger)
val cleanTerm = term.transform {
case q@Quantification(_, _, _, triggers, _, _, _) if triggers.nonEmpty =>
val goodTriggers = triggers.filterNot(trig => trig.p.exists(ptrn => ptrn.shallowCollect {
val goodTriggers = triggers.filterNot(trig => trig.p.exists(ptrn =>
ptrn.isInstanceOf[Var] || ptrn.shallowCollect {
case t => triggerGenerator.isForbiddenInTrigger(t)
}.nonEmpty))
q.copy(triggers = goodTriggers)
}()
}(_ => true)
cleanTerm
}

Expand Down
22 changes: 16 additions & 6 deletions src/main/scala/rules/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1416,13 +1416,15 @@ object evaluator extends EvaluationRules {
triggerAxioms = triggerAxioms ++ axioms
smDefs = smDefs ++ smDef
case pa: ast.PredicateAccess if s.heapDependentTriggers.contains(pa.loc(s.program)) =>
val (axioms, trigs, _) = generatePredicateTrigger(pa, s, pve, v)
val (axioms, trigs, _, smDef) = generatePredicateTrigger(pa, s, pve, v)
triggers = triggers ++ trigs
triggerAxioms = triggerAxioms ++ axioms
smDefs = smDefs ++ smDef
case wand: ast.MagicWand if s.heapDependentTriggers.contains(MagicWandIdentifier(wand, s.program)) =>
val (axioms, trigs, _) = generateWandTrigger(wand, s, pve, v)
val (axioms, trigs, _, smDef) = generateWandTrigger(wand, s, pve, v)
triggers = triggers ++ trigs
triggerAxioms = triggerAxioms ++ axioms
smDefs = smDefs ++ smDef
case e => evalTrigger(s, Seq(e), pve, v)((_, t, _) => {
triggers = triggers ++ t
Success()
Expand Down Expand Up @@ -1502,7 +1504,11 @@ object evaluator extends EvaluationRules {
}

/* TODO: Try to unify with generateFieldTrigger above, or at least with generateWandTrigger below */
private def generatePredicateTrigger(pa: ast.PredicateAccess, s: State, pve: PartialVerificationError, v: Verifier): (Seq[Term], Seq[Term], PredicateTrigger) = {
private def generatePredicateTrigger(pa: ast.PredicateAccess,
s: State,
pve: PartialVerificationError,
v: Verifier)
: (Seq[Term], Seq[Term], PredicateTrigger, Seq[SnapshotMapDefinition]) = {
var axioms = Seq.empty[Term]
var triggers = Seq.empty[Term]
var mostRecentTrig: PredicateTrigger = null
Expand All @@ -1524,11 +1530,15 @@ object evaluator extends EvaluationRules {
Success()
})

(axioms, triggers, mostRecentTrig)
(axioms, triggers, mostRecentTrig, Seq(smDef1))
}

/* TODO: See comments for generatePredicateTrigger above */
private def generateWandTrigger(wand: ast.MagicWand, s: State, pve: PartialVerificationError, v: Verifier): (Seq[Term], Seq[Term], PredicateTrigger) = {
private def generateWandTrigger(wand: ast.MagicWand,
s: State,
pve: PartialVerificationError,
v: Verifier)
: (Seq[Term], Seq[Term], PredicateTrigger, Seq[SnapshotMapDefinition]) = {
var axioms = Seq.empty[Term]
var triggers = Seq.empty[Term]
var mostRecentTrig: PredicateTrigger = null
Expand All @@ -1552,7 +1562,7 @@ object evaluator extends EvaluationRules {
Success()
})

(axioms, triggers, mostRecentTrig)
(axioms, triggers, mostRecentTrig, Seq(smDef1))
}

/* Evaluate a sequence of expressions in Order
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/rules/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ object executor extends ExecutionRules {
val (relevantChunks, otherChunks) =
quantifiedChunkSupporter.splitHeap[QuantifiedFieldChunk](s2.h, BasicChunkIdentifier(field.name))
val hints = quantifiedChunkSupporter.extractHints(None, Seq(tRcvr))
val chunkOrderHeuristics = quantifiedChunkSupporter.hintBasedChunkOrderHeuristic(hints)
val chunkOrderHeuristics = quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(Seq(tRcvr), hints, v2)
val s2p = if (s2.heapDependentTriggers.contains(field)){
val (smDef1, smCache1) =
quantifiedChunkSupporter.summarisingSnapshotMap(
Expand Down
36 changes: 34 additions & 2 deletions src/main/scala/rules/QuantifiedChunkSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1231,8 +1231,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
case Some(heuristics) =>
heuristics
case None =>
quantifiedChunkSupporter.hintBasedChunkOrderHeuristic(
quantifiedChunkSupporter.extractHints(None, arguments))
quantifiedChunkSupporter.singleReceiverChunkOrderHeuristic(arguments,
quantifiedChunkSupporter.extractHints(None, arguments), v)
}

if (s.exhaleExt) {
Expand Down Expand Up @@ -1737,6 +1737,38 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
matchingChunks ++ otherChunks
}

def singleReceiverChunkOrderHeuristic(receiver: Seq[Term], hints: Seq[Term], v: Verifier)
: Seq[QuantifiedBasicChunk] => Seq[QuantifiedBasicChunk] = {
// Heuristic that emulates greedy Silicon behavior for consuming single-receiver permissions.
// First: Find singleton chunks that have the same receiver syntactically.
// If so, consider those first, then all others.
// Second: If nothing matches syntactically, try to find a chunk that matches the receiver using the decider.
// If that's the case, consider that chunk first, then all others.
// Third: As a fallback, use the standard hint based heuristics.
val fallback = hintBasedChunkOrderHeuristic(hints)

(chunks: Seq[QuantifiedBasicChunk]) => {
val (syntacticMatches, others) = chunks.partition(c => c.singletonArguments.contains(receiver))
if (syntacticMatches.nonEmpty) {
syntacticMatches ++ others
} else {
val greedyMatch = chunks.find(c => c.singletonArguments match {
case Some(args) if args.length == receiver.length =>
args.zip(receiver).forall(ts => v.decider.check(ts._1 === ts._2, Verifier.config.checkTimeout()))
case _ =>
false
}).toSeq
if (greedyMatch.nonEmpty) {
greedyMatch ++ chunks.diff(greedyMatch)
} else {
// It doesn't seem to be any of the singletons. Use the fallback on the non-singletons.
val (qpChunks, singletons) = chunks.partition(_.singletonArguments.isEmpty)
fallback(qpChunks) ++ singletons
}
}
}
}

def extractHints(cond: Option[Term], arguments: Seq[Term]): Seq[Term] = {
var hints =
arguments.flatMap {
Expand Down

0 comments on commit 1c3b91e

Please sign in to comment.