From 222499c615256d3201d1c1b7f873f0b34732169a Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:31:56 +0200 Subject: [PATCH 01/43] Use a Map from Snap to Snap to represent a magic wand snapshot. --- src/main/scala/Utils.scala | 1 + .../decider/TermToSMTLib2Converter.scala | 22 +- src/main/scala/rules/MagicWandSupporter.scala | 234 ++++++++++-------- src/main/scala/state/Terms.scala | 67 ++--- src/main/scala/state/Utils.scala | 2 +- .../BuiltinDomainsContributor.scala | 96 ++++--- .../supporters/DefaultMapsContributor.scala | 13 + .../supporters/DefaultSetsContributor.scala | 6 + 8 files changed, 239 insertions(+), 202 deletions(-) diff --git a/src/main/scala/Utils.scala b/src/main/scala/Utils.scala index 56a773538..f7f817f2d 100644 --- a/src/main/scala/Utils.scala +++ b/src/main/scala/Utils.scala @@ -222,6 +222,7 @@ package object utils { case class ViperEmbedding(embeddedSort: Sort) extends silver.ast.ExtensionType { def substitute(typVarsMap: Predef.Map[silver.ast.TypeVar, silver.ast.Type]): silver.ast.Type = this def isConcrete: Boolean = true + override def toString: String = s"ViperEmbedding(sorts.$embeddedSort)" } } diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index b8f6c0f89..4074af20c 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -261,15 +261,16 @@ class TermToSMTLib2Converter case Domain(id, fvf) => parens(text("$FVF.domain_") <> id <+> render(fvf)) - case Lookup(field, fvf, at) => //fvf.sort match { -// case _: sorts.PartialFieldValueFunction => - parens(text("$FVF.lookup_") <> field <+> render(fvf) <+> render(at)) -// case _: sorts.TotalFieldValueFunction => -// render(Apply(fvf, Seq(at))) -// parens("$FVF.lookup_" <> field <+> render(fvf) <+> render(at)) -// case _ => -// sys.error(s"Unexpected sort '${fvf.sort}' of field value function '$fvf' in lookup term '$term'") -// } + case Lookup(field, fvf, at) => + // fvf.sort match { + // case _: sorts.PartialFieldValueFunction => + parens(text("$FVF.lookup_") <> field <+> render(fvf) <+> render(at)) + // case _: sorts.TotalFieldValueFunction => + // render(Apply(fvf, Seq(at))) + // parens("$FVF.lookup_" <> field <+> render(fvf) <+> render(at)) + // case _ => + // sys.error(s"Unexpected sort '${fvf.sort}' of field value function '$fvf' in lookup term '$term'") + // } case FieldTrigger(field, fvf, at) => parens(text("$FVF.loc_") <> field <+> (fvf.sort match { case sorts.FieldValueFunction(_, _) => render(Lookup(field, fvf, at)) <+> render(at) @@ -313,6 +314,9 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) + case MagicWandSnapshot(wandMap) => + render(wandMap) + case _: MagicWandChunkTerm | _: Quantification => sys.error(s"Unexpected term $term cannot be translated to SMTLib code") diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 844f67a74..a7cc6ce17 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -79,25 +79,6 @@ object magicWandSupporter extends SymbolicExecutionRules { // result // } - //TODO: needs to calculate a snapshot that preserves values from the lhs - def createChunk(s: State, - wand: ast.MagicWand, - pve: PartialVerificationError, - v: Verifier) - (Q: (State, MagicWandChunk, Verifier) => VerificationResult) - : VerificationResult = - createChunk(s, wand, MagicWandSnapshot(freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v)), pve, v)(Q) - - def createChunk(s: State, - wand: ast.MagicWand, - abstractLhs: Term, - rhsSnapshot: Term, - pve: PartialVerificationError, - v: Verifier) - (Q: (State, MagicWandChunk, Verifier) => VerificationResult) - : VerificationResult = - createChunk(s, wand, MagicWandSnapshot(abstractLhs, rhsSnapshot), pve, v)(Q) - def createChunk(s: State, wand: ast.MagicWand, snap: MagicWandSnapshot, @@ -191,9 +172,6 @@ object magicWandSupporter extends SymbolicExecutionRules { } } -// private var cnt = 0L -// private val packageLogger = LoggerFactory.getLogger("package") - def packageWand(state: State, wand: ast.MagicWand, proofScript: ast.Seqn, @@ -202,41 +180,15 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Chunk, Verifier) => VerificationResult) : VerificationResult = { - /* TODO: Logging code is very similar to that in HeuristicsSupporter. Unify. */ - -// val myId = cnt; cnt += 1 -// val baseIdent = " " -// var printedHeader = false - -// def lnsay(msg: String, ident: Int = 1) { -// val prefix = "\n" + (if (ident == 0) "" else baseIdent) -// dosay(prefix, msg, ident - 1) -// } -// -// def say(msg: String, ident: Int = 1) { -// val prefix = if (ident == 0) "" else baseIdent -// dosay(prefix, msg, ident - 1) -// } -// -// def dosay(prefix: String, msg: String, ident: Int) { -// if (!printedHeader) { -// packageLogger.debug(s"\n[packageWand $myId]") -// printedHeader = true -// } -// -// val messagePrefix = baseIdent * ident -// packageLogger.debug(s"$prefix$messagePrefix $msg") -// } -// -// say(s"wand = $wand") -// say("c.reserveHeaps:") -// s.reserveHeaps.map(v.stateFormatter.format).foreach(str => say(str, 2)) - val s = if (state.exhaleExt) state else state.copy(reserveHeaps = Heap() :: state.h :: Nil) + // v.logger.debug(s"wand = $wand") + // v.logger.debug("c.reserveHeaps:") + // s.reserveHeaps.map(v.stateFormatter.format).foreach(str => v.logger.debug(str, 2)) + val stackSize = 3 + s.reserveHeaps.tail.size - /* IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below */ + // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below var results: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[RecordedPathConditions], Chunk)] = Nil /* TODO: When parallelising branches, some of the runtime assertions in the code below crash @@ -250,52 +202,60 @@ object magicWandSupporter extends SymbolicExecutionRules { recordPcs = true, parallelizeBranches = false) - def createWandChunkAndRecordResults(s4: State, - freshSnapRoot: Var, - snap: Term, - v3: Verifier) - : VerificationResult = { + def appendToResults(s5: State, ch: Chunk, pcs: RecordedPathConditions, v4: Verifier): Unit = { + assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") - def appendToResults(s5: State, ch: Chunk, pcs: RecordedPathConditions, v4: Verifier): Unit = { - assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") + var conservedPcs: Vector[RecordedPathConditions] = Vector.empty + var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs - var conservedPcs: Vector[RecordedPathConditions] = Vector.empty - var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs + // Producing a wand's LHS and executing the packaging proof code can introduce definitional path conditions, e.g. + // new permission and snapshot maps, which are in general necessary to proceed after the + // package statement, e.g. to know which permissions have been consumed. + // Here, we want to keep *only* the definitions, but no other path conditions. - // Producing a wand's LHS and executing the packaging proof code can introduce definitional path conditions, e.g. - // new permission and snapshot maps, which are in general necessary to proceed after the - // package statement, e.g. to know which permissions have been consumed. - // Here, we want to keep *only* the definitions, but no other path conditions. + conservedPcs = s5.conservedPcs.head :+ pcs.definitionsOnly - conservedPcs = s5.conservedPcs.head :+ pcs.definitionsOnly + conservedPcsStack = + s5.conservedPcs.tail match { + case empty @ Seq() => empty + case head +: tail => (head ++ conservedPcs) +: tail + } - conservedPcsStack = - s5.conservedPcs.tail match { - case empty @ Seq() => empty - case head +: tail => (head ++ conservedPcs) +: tail - } + val s6 = s5.copy(conservedPcs = conservedPcsStack, recordPcs = s.recordPcs) - val s6 = s5.copy(conservedPcs = conservedPcsStack, recordPcs = s.recordPcs) - - results :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) - } + results :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) + } + def createWandChunkAndRecordResults(s4: State, + freshSnapRoot: Var, + snap: Term, + v3: Verifier) + : VerificationResult = { val preMark = v3.decider.setPathConditionMark() + v3.logger.debug(s"\npackageWand -> createWandChunkAndRecordResults: Create MagicWandSnapshot from freshSnapRoot $freshSnapRoot and snap $snap\n") + + // TODO: Find better solution to lift the definition of the magic wand snapshot into a wider scope. + v3.decider.popScope() + val wandSnapshot = createMagicWandSnapshot(freshSnapRoot, snap, v3) + v3.decider.pushScope() + + // If the wand is part of a quantified expression if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { val bodyVars = wand.subexpressionsToEvaluate(s.program) val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) + evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { - val (sm, smValueDef) = - quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, MagicWandSnapshot(freshSnapRoot, snap), v4) + val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, wandSnapshot, v4) v4.decider.prover.comment("Definitional axioms for singleton-SM's value") v4.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) appendToResults(s5, ch, v4.decider.pcs.after(preMark), v4) Success() }) + } else { - magicWandSupporter.createChunk(s4, wand, freshSnapRoot, snap, pve, v3)((s5, ch, v4) => { -// say(s"done: create wand chunk: $ch") + magicWandSupporter.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { + // v.logger.debug(s"done: create wand chunk: $ch") appendToResults(s5, ch, v4.decider.pcs.after(preMark), v4) Success() }) @@ -303,14 +263,15 @@ object magicWandSupporter extends SymbolicExecutionRules { } val r = executionFlowController.locally(sEmp, v)((s1, v1) => { - /* Using conservingSnapshotGeneration a snapshot (binary tree) will be - * constructed using First/Second datatypes, that preserves the original root. - * The leafs of this tree will later appear in the snapshot of the rhs at the - * appropriate places. Thus equating freshSnapRoot with the snapshot received - * from consuming the lhs when applying the wand preserves values from the lhs - * into the rhs. + /* A snapshot (binary tree) will be constructed using First/Second datatypes, + * that preserves the original root. The leafs of this tree will later appear + * in the snapshot of the RHS at the appropriate places. Thus equating + * `freshSnapRoot` with the snapshot received from consuming the LHS when + * applying the wand preserves values from the LHS into the RHS. */ val freshSnapRoot = freshSnap(sorts.Snap, v1) + + // Produce the wand's LHS. produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() @@ -331,25 +292,35 @@ object magicWandSupporter extends SymbolicExecutionRules { * empty, and where the dots represent the heaps belonging to surrounding package/packaging * operations. hOps will be populated while processing the RHS of the wand to package. * More precisely, each ghost operation (folding, applying, etc.) that is executed - * populates hUsed (by transferring permissions from heaps lower in the stack, and by - * adding new chunks, e.g. a folded predicate) during its execution, and afterwards - * merges hUsed and hOps, the result of which replaces hOps, and hUsed is replaced by a - * new empty heap (see also the final state updates in, e.g. method `applyingWand` - * or `unfoldingPredicate` below). + * populates hUsed during its execution. This is done by transferring permissions + * from heaps lower in the stack, and by adding new chunks, e.g. a folded predicate. + * Afterwards, it merges hUsed and hOps, which replaces hOps. hUsed is replaced by a + * new empty heap. See also the final state updates in, e.g. method `applyingWand` + * or `unfoldingPredicate` below. */ assert(stackSize == s2.reserveHeaps.length) -// say(s"done: produced LHS ${wand.left}") -// say(s"next: consume RHS ${wand.right}") + // v.logger.debug(s"done: produced LHS ${wand.left}") + // v.logger.debug(s"next: consume RHS ${wand.right}") + executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { consume(proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, pve, proofScriptVerifier)((s3, snap, v3) => { -// say(s"done: consumed RHS ${wand.right}") + + // v.logger.debug(s"done: consumed RHS ${wand.right}") + val s4 = s3.copy(//h = s.h, /* Temporarily */ exhaleExt = false, oldHeaps = s.oldHeaps) -// say(s"next: create wand chunk") - createWandChunkAndRecordResults(s4, freshSnapRoot, snap, v3)})})})}) + // v.logger.debug(s"next: create wand chunk $freshSnapRoot") + + createWandChunkAndRecordResults(s4, freshSnapRoot, snap, v3) + }) + }) + }) + }) + + // v.logger.debug(s"\npackageWand -> results (${results.length}): $results\n") if (results.isEmpty) { // No results mean that packaging the wand resulted in inconsistent states on all paths, // and thus, that no wand chunk was created. In order to continue, we create one now. @@ -371,7 +342,10 @@ object magicWandSupporter extends SymbolicExecutionRules { executionFlowController.locally(s1, v)((s2, v1) => { v1.decider.setCurrentBranchCondition(And(branchConditions), Some(viper.silicon.utils.ast.BigAnd(branchConditionsExp.flatten))) conservedPcs.foreach(pcs => v1.decider.assume(pcs.conditionalized)) - Q(s2, magicWandChunk, v1)})}}) + Q(s2, magicWandChunk, v1) + }) + } + }) } def applyWand(s: State, @@ -380,19 +354,40 @@ object magicWandSupporter extends SymbolicExecutionRules { v: Verifier) (Q: (State, Verifier) => VerificationResult) : VerificationResult = { - consume(s, wand, pve, v)((s1, snap, v1) => { - val wandSnap = MagicWandSnapshot(snap) - consume(s1, wand.left, pve, v1)((s2, snap, v2) => { - /* It is assumed that snap and wandSnap.abstractLhs are structurally the same. - * Since a wand can only be applied once, equating the two snapshots is sound. - */ - assert(snap.sort == sorts.Snap, s"expected snapshot but found: $snap") - v2.decider.assume(snap === wandSnap.abstractLhs) - val s3 = s2.copy(oldHeaps = s1.oldHeaps + (Verifier.MAGIC_WAND_LHS_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) - produce(s3.copy(conservingSnapshotGeneration = true), toSf(wandSnap.rhsSnapshot), wand.right, pve, v2)((s4, v3) => { - val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) - val s6 = v3.stateConsolidator(s5).consolidate(s5, v3).copy(oldHeaps = s1.oldHeaps) - Q(s6, v3)})})})} + // Consume the magic wand instance "A --* B". + consume(s, wand, pve, v)((s1, snap, v1) => { + // Wrap snapshot inside MagicWandSnapshot class. + // For now: assuming that snap is already a MagicWandSnapshot + // TODO: handle situations where snapshot was inhaled and is not a MagicWandSnapshot yet. + val wandSnap = MagicWandSnapshot(snap) + + // Consume the wand's LHS "A". + consume(s1, wand.left, pve, v1)((s2, snap, v2) => { + /* It is assumed that snap and wandSnap.abstractLhs are structurally the same. + * Equating the two snapshots is sound iff a wand is applied only once. + * Older solution in this case did use this assumption: + * v2.decider.assume(snap === wandSnap.abstractLhs) + */ + assert(snap.sort == sorts.Snap, s"expected snapshot but found: $snap") + + // Create copy of the state with a new labelled heap (i.e. `oldHeaps`) called "lhs". + val s3 = s2.copy(oldHeaps = s1.oldHeaps + (Verifier.MAGIC_WAND_LHS_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) + + // Produce the wand's RHS. + produce(s3.copy(conservingSnapshotGeneration = true), toSf(MapLookup(wandSnap.wandMap, snap)), wand.right, pve, v2)((s4, v3) => { + // Recreate old state without the magic wand, and the state with the oldHeap called lhs. + val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) + + // Merge as many chunks as possible where we can deduce that they must be aliases + // from their permissions, or add permissions that we can infer. + // Afterward also remove labelled old heap "lhs". + val s6 = v3.stateConsolidator(s5).consolidate(s5, v3).copy(oldHeaps = s1.oldHeaps) + + Q(s6, v3) + }) + }) + }) + } def transfer[CH <: Chunk] (s: State, @@ -477,4 +472,23 @@ object magicWandSupporter extends SymbolicExecutionRules { s.reserveCfgs.head.outEdges(b) else s.methodCfg.outEdges(b) + + /** + * Define wand for all possible snapshots. + * + * @param abstractLhs Logic variable representing the abstract left hand side of the wand. + * @param rhsSnapshot Snapshot of the right hand side of the wand which implements values from the left hand side at the appropriate places. + * @param v Verifier with which a variable is created in the current context. + * @return The snapshot of the wand. + */ + private def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { + v.decider.prover.comment("Create new magic wand snapshot map") + + // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, + // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. + val wandMap = v.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap)) + v.decider.assume(Forall(abstractLhs, MapLookup(wandMap, abstractLhs) === rhsSnapshot, Trigger(MapLookup(wandMap, abstractLhs)))) + + MagicWandSnapshot(wandMap) + } } diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index ed00e5541..a2bf53869 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2295,48 +2295,51 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T /* Magic wands */ -class MagicWandSnapshot(val abstractLhs: Term, val rhsSnapshot: Term) extends Combine(abstractLhs, rhsSnapshot) { - utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) - utils.assertSort(rhsSnapshot, "rhs", sorts.Snap) +/** + * Represents a snapshot of a magic wand, which is a map from Snap to Snap. + * + * @param wandMap The map that represents the snapshot of the magic wand. It is a variable of sort Map(Snap, Snap). + */ +class MagicWandSnapshot(val wandMap: Term) extends SnapshotTerm with ConditionalFlyweight[Term, MagicWandSnapshot] { + utils.assertSort(wandMap, "wand map", sorts.Map(sorts.Snap, sorts.Snap)) - override lazy val toString = s"wandSnap(lhs = $abstractLhs, rhs = $rhsSnapshot)" + override lazy val toString = s"wandSnap(wandMap = $wandMap)" - def merge(other: MagicWandSnapshot, branchConditions: Stack[Term]): MagicWandSnapshot = { - assert(this.abstractLhs == other.abstractLhs) - val condition = And(branchConditions) - MagicWandSnapshot(this.abstractLhs, if (this.rhsSnapshot == other.rhsSnapshot) - this.rhsSnapshot - else - Ite(condition, other.rhsSnapshot, this.rhsSnapshot)) - } + override val equalityDefiningMembers: Term = wandMap } object MagicWandSnapshot { - def apply(snapshot: Term): MagicWandSnapshot = { - assert(snapshot.sort == sorts.Snap) - snapshot match { - case snap: MagicWandSnapshot => snap - case _ => - MagicWandSnapshot(First(snapshot), Second(snapshot)) - } - } - - // Since MagicWandSnapshot subclasses Combine, we apparently cannot inherit the normal subclass, so we - // have to copy paste the code here. + /** + * Since MagicWandSnapshot subclasses Combine, we cannot inherit the usual subclass + * [[viper.silicon.state.terms.GeneralCondFlyweightFactory]], so we have to copy&paste the code here. + */ var pool = new TrieMap[(Term, Term), MagicWandSnapshot]() - def createIfNonExistent(args: (Term, Term)): MagicWandSnapshot = { - if (Verifier.config.useFlyweight) { - pool.getOrElseUpdate(args, actualCreate(args)) - } else { - actualCreate(args) + /** + * Create a new MagicWandSnapshot instance. + * + * See helper method [[viper.silicon.rules.magicWandSupporter.createMagicWandSnapshot]] + * for more information on how to create a MagicWandSnapshot. + * + * @param snapshot A MagicWandSnapshot instance or a variable to a Map(Snap, Snap). + * @return The resulting MagicWandSnapshot instance. + */ + def apply(snapshot: Term): MagicWandSnapshot = { + snapshot match { + case snap: MagicWandSnapshot => { + assert(snapshot.sort == sorts.Snap, s"Expected snapshot to be of sort Snap, but got ${snapshot.sort}") + snap + } + case wandMap: Var => { + utils.assertSort(wandMap, "wandMap", sorts.Map(sorts.Snap, sorts.Snap)) + new MagicWandSnapshot(wandMap) + } + // TODO: Create a new MagicWandSnapshot instance from a pure Snapshot, i.e. when inhaling a magic wand. + // This map should take First(snapshot) as key and merge them with Second(snapshot) } } - def actualCreate(tuple: (Term, Term)) = new MagicWandSnapshot(tuple._1, tuple._2) - def apply(fst: Term, snd: Term): MagicWandSnapshot = createIfNonExistent((fst, snd)) - - def unapply(mws: MagicWandSnapshot) = Some((mws.abstractLhs, mws.rhsSnapshot)) + def unapply(wandSnapshot: MagicWandSnapshot): Option[Term] = Some(wandSnapshot.wandMap) } class MagicWandChunkTerm(val chunk: MagicWandChunk) extends Term with ConditionalFlyweight[MagicWandChunk, MagicWandChunkTerm] { diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index 38a1abbff..f2f0b00f6 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -196,7 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(lhs, rhs) => MagicWandSnapshot(go(lhs), go(rhs)) + case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) case Second(t) => Second(go(t)) diff --git a/src/main/scala/supporters/BuiltinDomainsContributor.scala b/src/main/scala/supporters/BuiltinDomainsContributor.scala index f181a298a..4df6ddadd 100644 --- a/src/main/scala/supporters/BuiltinDomainsContributor.scala +++ b/src/main/scala/supporters/BuiltinDomainsContributor.scala @@ -8,7 +8,6 @@ package viper.silicon.supporters import java.io.File import java.net.URL - import scala.annotation.unused import scala.reflect.ClassTag import viper.silver.ast @@ -17,6 +16,7 @@ import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.DefaultSymbolConverter import viper.silicon.state.terms._ +import viper.silver.ast.DomainType abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, DomainFun, Term] { type BuiltinDomainType <: ast.GenericType @@ -57,38 +57,20 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai def analyze(program: ast.Program): Unit = { val builtinDomainTypeInstances = computeGroundTypeInstances(program) - val sourceProgram = utils.loadProgramFromUrl(sourceUrl) + val sourceProgram = loadProgramFromUrl(sourceUrl) val sourceDomain = transformSourceDomain(sourceProgram.findDomain(sourceDomainName)) + // List of all domains found in the program with their instantiations, i.e. the types they are used with val sourceDomainTypeInstances = builtinDomainTypeInstances map (builtinTypeInstance => { - val instantiation : Map[viper.silver.ast.TypeVar,viper.silver.ast.Type] = sourceDomain.typVars.zip(builtinTypeInstance.typeArguments).toMap - //(instantiation, ast.DomainType(sourceDomain, instantiation)) + val instantiation: Map[viper.silver.ast.TypeVar, viper.silver.ast.Type] = sourceDomain.typVars.zip(builtinTypeInstance.typeArguments).toMap ast.DomainType(sourceDomain, instantiation) }) - /* For each necessary domain type, instantiate the corresponding domain */ - val sourceDomainInstantiationsWithType = - sourceDomainTypeInstances map (mdt => { - /* TODO: Copied from DomainInstances.getInstanceMembers. - * Cannot directly use that because it filters according to which domain instances - * are used in the program from which the source domain was loaded, whereas the - * instances should be filtered according to which are used in the program under - * verification. - */ - val functions = sourceDomain.functions.map(ast.utility.DomainInstances.substitute(_, mdt.typVarsMap, sourceProgram)).distinct - val axioms = sourceDomain.axioms.map(ast.utility.DomainInstances.substitute(_, mdt.typVarsMap, sourceProgram)).distinct - - val instance = - sourceDomain.copy(functions = functions, axioms = axioms)(sourceDomain.pos, sourceDomain.info, sourceDomain.errT) - - (mdt, transformSourceDomainInstance(instance, mdt)) - }) - - val sourceDomainInstantiations = sourceDomainInstantiationsWithType.map(x => x._2) + val sourceDomainInstantiationsWithType = instantiateWithDomain(sourceProgram, sourceDomain, sourceDomainTypeInstances) collectSorts(sourceDomainTypeInstances) - collectFunctions(sourceDomainInstantiations, program) + collectFunctions(sourceDomainInstantiationsWithType, program) collectAxioms(sourceDomainInstantiationsWithType) } @@ -97,6 +79,26 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai case builtinDomainTypeTag(s) => s }) + /** + * For each necessary domain type, instantiate the corresponding domain + */ + private def instantiateWithDomain(sourceProgram: ast.Program, sourceDomain: ast.Domain, sourceDomainTypeInstances: InsertionOrderedSet[DomainType]): Set[(ast.DomainType, ast.Domain)] = { + sourceDomainTypeInstances map (domainType => { + /* TODO: Copied from DomainInstances.getInstanceMembers. + * Cannot directly use that because it filters according to which domain instances + * are used in the program from which the source domain was loaded, whereas the + * instances should be filtered according to which are used in the program under + * verification. + */ + val functions = sourceDomain.functions.map(ast.utility.DomainInstances.substitute(_, domainType.typVarsMap, sourceProgram)).distinct + val axioms = sourceDomain.axioms.map(ast.utility.DomainInstances.substitute(_, domainType.typVarsMap, sourceProgram)).distinct + + val instance = sourceDomain.copy(functions = functions, axioms = axioms)(sourceDomain.pos, sourceDomain.info, sourceDomain.errT) + + (domainType, transformSourceDomainInstance(instance, domainType)) + }) + } + protected def transformSourceDomain(sourceDomain: ast.Domain): ast.Domain = sourceDomain protected def transformSourceDomainInstance(sourceDomain: ast.Domain, @unused typ: ast.DomainType): ast.Domain = sourceDomain @@ -110,9 +112,9 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai }) } - protected def collectFunctions(domains: Set[ast.Domain], program: ast.Program): Unit = { - domains foreach ( - _.functions foreach (df => + protected def collectFunctions(domains: Set[(ast.DomainType, ast.Domain)], program: ast.Program): Unit = { + domains foreach (d => + d._2.functions foreach (df => if (df.interpretation.isEmpty) collectedFunctions += symbolConverter.toFunction(df, program).asInstanceOf[DomainFun])) } @@ -129,7 +131,7 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai * are preserved. */ val domainName = f"${d.domainName}[${d.typVarsMap.values.map(t => symbolConverter.toSort(t)).mkString(",")}]" - domainTranslator.translateAxiom(ax, symbolConverter.toSort, true).transform { + domainTranslator.translateAxiom(ax, symbolConverter.toSort, builtin = true).transform { case q@Quantification(_,_,_,_,name,_,_) if name != "" => q.copy(name = f"${domainName}_${name}") case Equals(t1, t2) => BuiltinEquals(t1, t2) @@ -146,37 +148,19 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai collectedFunctions def declareSymbolsAfterAnalysis(sink: ProverLike): Unit = { - collectedFunctions foreach (f => sink.declare(FunctionDecl(f))) + symbolsAfterAnalysis foreach (f => sink.declare(FunctionDecl(f))) } def axiomsAfterAnalysis: Iterable[Term] = collectedAxioms def emitAxiomsAfterAnalysis(sink: ProverLike): Unit = { - collectedAxioms foreach (ax => sink.assume(ax)) - } - - def updateGlobalStateAfterAnalysis(): Unit = { /* Nothing to contribute*/ } -} - -class BuiltinDomainAwareSymbolConverter(sourceDomainName: String, - targetSortFactory: Iterable[Sort] => Sort) - extends DefaultSymbolConverter { - - override def toSort(typ: ast.Type): Sort = typ match { - case dt: ast.DomainType if dt.domainName == sourceDomainName => - targetSortFactory(dt.typVarsMap.values map toSort) - case other => - super.toSort(other) + axiomsAfterAnalysis foreach (ax => sink.assume(ax)) } -} -private object utils { - def loadProgramFromResource(resource: String): ast.Program = { - loadProgramFromUrl(getClass.getResource(resource)) - } + /* Utility */ // TODO: Check that Silver's parser doesn't already provide suitable functionality. - def loadProgramFromUrl(url: URL): ast.Program = { + private def loadProgramFromUrl(url: URL): ast.Program = { assert(url != null, s"Unexpectedly found sourceUrl == null") val fromPath = viper.silver.utility.Paths.pathFromResource(url) @@ -204,3 +188,15 @@ private object utils { program } } + +class BuiltinDomainAwareSymbolConverter(sourceDomainName: String, + targetSortFactory: Iterable[Sort] => Sort) + extends DefaultSymbolConverter { + + override def toSort(typ: ast.Type): Sort = typ match { + case dt: ast.DomainType if dt.domainName == sourceDomainName => + targetSortFactory(dt.typVarsMap.values map toSort) + case other => + super.toSort(other) + } +} diff --git a/src/main/scala/supporters/DefaultMapsContributor.scala b/src/main/scala/supporters/DefaultMapsContributor.scala index f41a5fbaa..606d5196b 100644 --- a/src/main/scala/supporters/DefaultMapsContributor.scala +++ b/src/main/scala/supporters/DefaultMapsContributor.scala @@ -8,8 +8,10 @@ package viper.silicon.supporters import scala.reflect.{ClassTag, classTag} import viper.silicon.Config +import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silver.ast import viper.silicon.state.terms.{Sort, Term, sorts} +import viper.silicon.utils.ast.ViperEmbedding class DefaultMapsContributor(val domainTranslator: DomainsTranslator[Term], config: Config) extends BuiltinDomainsContributor { @@ -27,4 +29,15 @@ class DefaultMapsContributor(val domainTranslator: DomainsTranslator[Term], conf assert(argumentSorts.size == 2) sorts.Map(argumentSorts.head, argumentSorts.tail.head) } + + override def computeGroundTypeInstances(program: ast.Program): InsertionOrderedSet[BuiltinDomainType] = { + var setTypeInstances = super.computeGroundTypeInstances(program) + + // Packaging and applying magic wands depends on Maps of type [[viper.silicon.state.terms.sorts.Snap]] + if (program.existsDefined { case ast.MagicWand(_, _) => true }) { + setTypeInstances ++= InsertionOrderedSet(Set(ast.MapType(ViperEmbedding(sorts.Snap), ViperEmbedding(sorts.Snap)))) + } + + setTypeInstances + } } diff --git a/src/main/scala/supporters/DefaultSetsContributor.scala b/src/main/scala/supporters/DefaultSetsContributor.scala index 3a14ea459..0fe8f276f 100644 --- a/src/main/scala/supporters/DefaultSetsContributor.scala +++ b/src/main/scala/supporters/DefaultSetsContributor.scala @@ -12,6 +12,7 @@ import viper.silver.ast import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.state.terms.{Sort, Term, sorts} import viper.silicon.verifier.Verifier +import viper.silicon.utils.ast.ViperEmbedding class DefaultSetsContributor(val domainTranslator: DomainsTranslator[Term], config: Config) extends BuiltinDomainsContributor { @@ -66,6 +67,11 @@ class DefaultSetsContributor(val domainTranslator: DomainsTranslator[Term], conf case ast.MapType(keyType, valueType) => Set(ast.SetType(keyType), ast.SetType(valueType)) }.flatten + // Packaging and applying magic wands depends on Sets of type [[viper.silicon.state.terms.sorts.Snap]] + if (program.existsDefined { case ast.MagicWand(_, _) => true }) { + setTypeInstances ++= InsertionOrderedSet(Set(ast.SetType(ViperEmbedding(sorts.Snap)))) + } + setTypeInstances } From cdb526fa49366d82eefd7aac7ac74b7f7a021e95 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 2 May 2024 17:31:29 +0200 Subject: [PATCH 02/43] Fix failing test cases. --- .../decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 197 ++++++++++-------- src/main/scala/rules/Producer.scala | 16 +- src/main/scala/state/Chunks.scala | 7 +- src/main/scala/state/Terms.scala | 58 +++--- src/main/scala/state/Utils.scala | 2 +- 6 files changed, 168 insertions(+), 114 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index 4074af20c..fe4920a8e 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -314,7 +314,7 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(wandMap) => + case MagicWandSnapshot(_, _, wandMap) => render(wandMap) case _: MagicWandChunkTerm diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index a7cc6ce17..2259752b6 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -79,6 +79,9 @@ object magicWandSupporter extends SymbolicExecutionRules { // result // } + /** + * Evaluate the wand's arguments and create a `MagicWandChunk` out of it. + */ def createChunk(s: State, wand: ast.MagicWand, snap: MagicWandSnapshot, @@ -91,6 +94,13 @@ object magicWandSupporter extends SymbolicExecutionRules { ) } + /** + * Evaluate all expressions inside the given magic wand instance in the current state. + * + * @param s State in which to expressions are evaluated. + * @param wand Magic Wand instance. + * @param Q Method whose second argument is used to return the evaluated terms of all expressions. + */ def evaluateWandArguments(s: State, wand: ast.MagicWand, pve: PartialVerificationError, @@ -128,7 +138,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val initial = (initialConsumptionResult, s, Stack.empty[Heap], Stack.empty[Option[CH]]) val (result, s1, heaps, consumedChunks) = hs.foldLeft[(ConsumptionResult, State, Stack[Heap], Stack[Option[CH]])](initial)((partialResult, heap) => - partialResult match { + partialResult match { case (r: Complete, sIn, hps, cchs) => (r, sIn, heap +: hps, None +: cchs) case (Incomplete(permsNeeded), sIn, hps, cchs) => val (success, sOut, h, cch) = consumeFunction(sIn, heap, permsNeeded, v) @@ -172,6 +182,25 @@ object magicWandSupporter extends SymbolicExecutionRules { } } + /** + * Package a magic wand into a chunk. It performs the computation of the wand's footprint + * and captures all values associated to these locations inside the wand's snapshot. + * + * {{{ + * package A --* B { } + * }}} + * + * For reference see Chapter 3 and 5 of [[http://malte.schwerhoff.de/docs/phd_thesis.pdf Malte Schwerhoff's PhD thesis]] + * and [[https://ethz.ch/content/dam/ethz/special-interest/infk/chair-program-method/pm/documents/Education/Theses/Nils_Becker_BA_report.pdf Nils Becker's Bachelor report]] + * + * @param state Current state. + * @param wand AST representation of the magic wand. + * @param proofScript AST of the proof script. The proof script contains instructions how we can construct the RHS given the LHS. + * @param pve Partial Verification Error that is used to report errors. + * @param v Verifier instance. + * @param Q Continuation-style function that is called with the resulting state and the chunk that was created. + * @return Result of the overall verification process. + */ def packageWand(state: State, wand: ast.MagicWand, proofScript: ast.Seqn, @@ -189,7 +218,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val stackSize = 3 + s.reserveHeaps.tail.size // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below - var results: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[RecordedPathConditions], Chunk)] = Nil + var recordedBranches: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[Term], Chunk)] = Nil /* TODO: When parallelising branches, some of the runtime assertions in the code below crash * during some executions - since such crashes are hard to debug, branch parallelisation @@ -202,10 +231,9 @@ object magicWandSupporter extends SymbolicExecutionRules { recordPcs = true, parallelizeBranches = false) - def appendToResults(s5: State, ch: Chunk, pcs: RecordedPathConditions, v4: Verifier): Unit = { + def appendToResults(s5: State, ch: Chunk, pcs: RecordedPathConditions, conservedPcs: Vector[Term], v4: Verifier): Unit = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") - var conservedPcs: Vector[RecordedPathConditions] = Vector.empty var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs // Producing a wand's LHS and executing the packaging proof code can introduce definitional path conditions, e.g. @@ -213,31 +241,27 @@ object magicWandSupporter extends SymbolicExecutionRules { // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - conservedPcs = s5.conservedPcs.head :+ pcs.definitionsOnly - conservedPcsStack = s5.conservedPcs.tail match { case empty @ Seq() => empty - case head +: tail => (head ++ conservedPcs) +: tail + case head +: tail => (head ++ (s5.conservedPcs.head :+ pcs.definitionsOnly)) +: tail } val s6 = s5.copy(conservedPcs = conservedPcsStack, recordPcs = s.recordPcs) - results :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) + recordedBranches :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) } def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, - snap: Term, + snapRhs: Term, v3: Verifier) : VerificationResult = { val preMark = v3.decider.setPathConditionMark() - v3.logger.debug(s"\npackageWand -> createWandChunkAndRecordResults: Create MagicWandSnapshot from freshSnapRoot $freshSnapRoot and snap $snap\n") - // TODO: Find better solution to lift the definition of the magic wand snapshot into a wider scope. - v3.decider.popScope() - val wandSnapshot = createMagicWandSnapshot(freshSnapRoot, snap, v3) - v3.decider.pushScope() + v3.decider.prover.comment(s"Create WandMap for wand $wand") + val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap))) + v3.decider.assumeDefinition(wandSnapshot.definition) // If the wand is part of a quantified expression if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -245,24 +269,39 @@ object magicWandSupporter extends SymbolicExecutionRules { val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { - val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, wandSnapshot, v4) + val snapshotTerm = Combine(freshSnapRoot, snapRhs) + val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, snapshotTerm, v4) v4.decider.prover.comment("Definitional axioms for singleton-SM's value") v4.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), v4) + val conservedPcs = (s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly).flatMap(_.conditionalized) + appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) } else { - magicWandSupporter.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { - // v.logger.debug(s"done: create wand chunk: $ch") - appendToResults(s5, ch, v4.decider.pcs.after(preMark), v4) + this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { + val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly + // Partition path conditions into a set which include the abstractLhs and those which do not + val (pcsWithAbstractLhs, pcsWithoutAbstractLhs) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(wandSnapshot.abstractLhs)) + // For all path conditions which include the abstractLhs, add those as part of the definition of the wandMap in the same forall quantifier + val pcsQuantified = Forall( + wandSnapshot.abstractLhs, + And(pcsWithAbstractLhs.map { + // Remove redundant forall quantifiers with the same quantified variable + case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body + case p => p + }), + Trigger(MapLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), + ) + + appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutAbstractLhs, v4) Success() }) } } - val r = executionFlowController.locally(sEmp, v)((s1, v1) => { + val tempResult = executionFlowController.locally(sEmp, v)((s1, v1) => { /* A snapshot (binary tree) will be constructed using First/Second datatypes, * that preserves the original root. The leafs of this tree will later appear * in the snapshot of the RHS at the appropriate places. Thus equating @@ -300,28 +339,23 @@ object magicWandSupporter extends SymbolicExecutionRules { */ assert(stackSize == s2.reserveHeaps.length) - // v.logger.debug(s"done: produced LHS ${wand.left}") - // v.logger.debug(s"next: consume RHS ${wand.right}") - + // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. + // The proof script should transform the current state such that we can consume the wand's RHS. executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { - consume(proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), wand.right, pve, proofScriptVerifier)((s3, snap, v3) => { - - // v.logger.debug(s"done: consumed RHS ${wand.right}") - - val s4 = s3.copy(//h = s.h, /* Temporarily */ - exhaleExt = false, - oldHeaps = s.oldHeaps) - - // v.logger.debug(s"next: create wand chunk $freshSnapRoot") - - createWandChunkAndRecordResults(s4, freshSnapRoot, snap, v3) + // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. + // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. + consume( + proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), + wand.right, pve, proofScriptVerifier + )((s3, snapRhs, v3) => { + + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, v3) }) }) }) }) - // v.logger.debug(s"\npackageWand -> results (${results.length}): $results\n") - if (results.isEmpty) { + if (recordedBranches.isEmpty) { // No results mean that packaging the wand resulted in inconsistent states on all paths, // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. @@ -329,25 +363,39 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) } - results.foldLeft(r)((res, packageOut) => { - res && { - val state = packageOut._1 - val branchConditions = packageOut._2 - val branchConditionsExp = packageOut._3 - val conservedPcs = packageOut._4 - val magicWandChunk = packageOut._5 - val s1 = state.copy(reserveHeaps = state.reserveHeaps.drop(3), + recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { + prevRes && { + val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState + val s1 = state.copy( + reserveHeaps = state.reserveHeaps.drop(3), parallelizeBranches = s.parallelizeBranches /* See comment above */ - /*branchConditions = c.branchConditions*/) + ) + + // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. executionFlowController.locally(s1, v)((s2, v1) => { + // Set the branch conditions v1.decider.setCurrentBranchCondition(And(branchConditions), Some(viper.silicon.utils.ast.BigAnd(branchConditionsExp.flatten))) - conservedPcs.foreach(pcs => v1.decider.assume(pcs.conditionalized)) + + // Recreate all path conditions in the Z3 proof script that we recorded for that branch + conservedPcs.foreach(pcs => v1.decider.assume(pcs)) + + // Execute the continuation Q Q(s2, magicWandChunk, v1) }) } }) } + /** + * Apply a magic wand to the current state. This consumes the magic wand itself and the LHS of the wand, and then produces the RHS. + * + * @param s Current state. + * @param wand The AST instance of the magic wand to apply. + * @param pve Partial Verification Error that is used to report errors. + * @param v Verifier instance. + * @param Q Continuation-style function that is called with the resulting state and the verification result. + * @return Result of the overall verification process. + */ def applyWand(s: State, wand: ast.MagicWand, pve: PartialVerificationError, @@ -355,32 +403,36 @@ object magicWandSupporter extends SymbolicExecutionRules { (Q: (State, Verifier) => VerificationResult) : VerificationResult = { // Consume the magic wand instance "A --* B". - consume(s, wand, pve, v)((s1, snap, v1) => { - // Wrap snapshot inside MagicWandSnapshot class. - // For now: assuming that snap is already a MagicWandSnapshot - // TODO: handle situations where snapshot was inhaled and is not a MagicWandSnapshot yet. - val wandSnap = MagicWandSnapshot(snap) - + consume(s, wand, pve, v)((s1, snapWand, v1) => { // Consume the wand's LHS "A". - consume(s1, wand.left, pve, v1)((s2, snap, v2) => { - /* It is assumed that snap and wandSnap.abstractLhs are structurally the same. + consume(s1, wand.left, pve, v1)((s2, snapLhs, v2) => { + /* It is assumed that snap and MagicWandSnapshot.abstractLhs are structurally the same. * Equating the two snapshots is sound iff a wand is applied only once. - * Older solution in this case did use this assumption: - * v2.decider.assume(snap === wandSnap.abstractLhs) + * The old solution in this case did use this assumption: + * v2.decider.assume(snap === snapWand.abstractLhs) */ - assert(snap.sort == sorts.Snap, s"expected snapshot but found: $snap") + assert(snapLhs.sort == sorts.Snap, s"expected snapshot but found: $snapLhs") // Create copy of the state with a new labelled heap (i.e. `oldHeaps`) called "lhs". - val s3 = s2.copy(oldHeaps = s1.oldHeaps + (Verifier.MAGIC_WAND_LHS_STATE_LABEL -> magicWandSupporter.getEvalHeap(s1))) + val s3 = s2.copy(oldHeaps = s1.oldHeaps + (Verifier.MAGIC_WAND_LHS_STATE_LABEL -> this.getEvalHeap(s1))) + + // If the snapWand is a (wrapped) MagicWandSnapshot then lookup the snapshot of the right-hand side by applying snapLhs. + val magicWandSnapshotLookup = snapWand match { + case snapshot: MagicWandSnapshot => snapshot.applyToWandMap(snapLhs) + case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToWandMap(snapLhs) + // Fallback solution for quantified magic wands + case predicateLookup: PredicateLookup => + v2.decider.assume(snapLhs === First(snapWand)) + Second(predicateLookup) + case _ => snapWand + } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(MapLookup(wandSnap.wandMap, snap)), wand.right, pve, v2)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) - // Merge as many chunks as possible where we can deduce that they must be aliases - // from their permissions, or add permissions that we can infer. - // Afterward also remove labelled old heap "lhs". + // Consolidate the state and remove labelled old heap "lhs". val s6 = v3.stateConsolidator(s5).consolidate(s5, v3).copy(oldHeaps = s1.oldHeaps) Q(s6, v3) @@ -411,7 +463,7 @@ object magicWandSupporter extends SymbolicExecutionRules { */ val preMark = v.decider.setPathConditionMark() executionFlowController.tryOrFail2[Stack[Heap], Stack[Option[CH]]](s, v)((s1, v1, QS) => - magicWandSupporter.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, failure, qvars, v1)(consumeFunction)(QS) + this.consumeFromMultipleHeaps(s1, s1.reserveHeaps.tail, perms, failure, qvars, v1)(consumeFunction)(QS) )((s2, hs2, chs2, v2) => { val conservedPcs = s2.conservedPcs.head :+ v2.decider.pcs.after(preMark) val s3 = s2.copy(conservedPcs = conservedPcs +: s2.conservedPcs.tail, reserveHeaps = s.reserveHeaps.head +: hs2) @@ -472,23 +524,4 @@ object magicWandSupporter extends SymbolicExecutionRules { s.reserveCfgs.head.outEdges(b) else s.methodCfg.outEdges(b) - - /** - * Define wand for all possible snapshots. - * - * @param abstractLhs Logic variable representing the abstract left hand side of the wand. - * @param rhsSnapshot Snapshot of the right hand side of the wand which implements values from the left hand side at the appropriate places. - * @param v Verifier with which a variable is created in the current context. - * @return The snapshot of the wand. - */ - private def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { - v.decider.prover.comment("Create new magic wand snapshot map") - - // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, - // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. - val wandMap = v.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap)) - v.decider.assume(Forall(abstractLhs, MapLookup(wandMap, abstractLhs) === rhsSnapshot, Trigger(MapLookup(wandMap, abstractLhs)))) - - MagicWandSnapshot(wandMap) - } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 7794de9c0..08a59ee93 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,8 +397,20 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snap = sf(sorts.Snap, v) - magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => + val snapRhs = sf(sorts.Map(sorts.Snap, sorts.Snap), v) + + // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, + // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. + v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") + val abstractLhs = v.decider.fresh(sorts.Snap) + val wandMap = v.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap)) + val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MapLookup(snapRhs, abstractLhs), wandMap) + + // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, + // is the same as our newly created wandMap. + v.decider.assumeDefinition(magicWandSnapshot.definition) + + magicWandSupporter.createChunk(s, wand, magicWandSnapshot, pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 2385d7935..44b907031 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -176,13 +176,16 @@ case class MagicWandChunk(id: MagicWandIdentifier, override val resourceID = MagicWandID override def withPerm(newPerm: Term) = MagicWandChunk(id, bindings, args, snap, newPerm) - override def withSnap(newSnap: Term) = MagicWandChunk(id, bindings, args, MagicWandSnapshot(newSnap), perm) + override def withSnap(newSnap: Term) = newSnap match { + case s: MagicWandSnapshot => MagicWandChunk(id, bindings, args, s, perm) + case _ => sys.error(s"MagicWand snapshot has to be of type MagicWandSnapshot but found ${newSnap.getClass}") + } override lazy val toString = { val pos = id.ghostFreeWand.pos match { case rp: viper.silver.ast.HasLineColumn => s"${rp.line}:${rp.column}" case other => other.toString } - s"wand@$pos[$snap; ${args.mkString(",")}]" + s"wand@$pos[$snap; ${args.mkString(", ")}]" } } diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index a2bf53869..1ffa9a89a 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2298,48 +2298,54 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T /** * Represents a snapshot of a magic wand, which is a map from Snap to Snap. * + * @param abstractLhs The term that represent the snapshot of the consumed left-hand side of the magic wand. + * It is used as a bound variable in a forall quantifier. + * @param rhsSnapshot The term that integrates the left-hand side in the snapshot that is produced after applying the magic wand. * @param wandMap The map that represents the snapshot of the magic wand. It is a variable of sort Map(Snap, Snap). + * In the symbolic execution this represents a function that when we apply a magic wand + * it consumes the left-hand side and uses the resulting snapshot to look up which right-hand side should be produced. */ -class MagicWandSnapshot(val wandMap: Term) extends SnapshotTerm with ConditionalFlyweight[Term, MagicWandSnapshot] { +class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { + utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) + utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) utils.assertSort(wandMap, "wand map", sorts.Map(sorts.Snap, sorts.Snap)) + override val sort: Sort = sorts.Map(sorts.Snap, sorts.Snap) + override lazy val toString = s"wandSnap(wandMap = $wandMap)" override val equalityDefiningMembers: Term = wandMap -} -object MagicWandSnapshot { /** - * Since MagicWandSnapshot subclasses Combine, we cannot inherit the usual subclass - * [[viper.silicon.state.terms.GeneralCondFlyweightFactory]], so we have to copy&paste the code here. + * Creates a term that can be used to define a `wandMap`. + * + * @return Expression which says that the map applied to an arbitrary lhs equals to the snapshot of the rhs. */ - var pool = new TrieMap[(Term, Term), MagicWandSnapshot]() + def definition: Term = Forall( + abstractLhs, + MapLookup(wandMap, abstractLhs) === rhsSnapshot, + Trigger(MapLookup(wandMap, abstractLhs)) + ) /** - * Create a new MagicWandSnapshot instance. + * Apply the given snapshot of the left-hand side to the magic wand map to get the snapshot of the right-hand side + * which includes the values of the left-hand side. * - * See helper method [[viper.silicon.rules.magicWandSupporter.createMagicWandSnapshot]] - * for more information on how to create a MagicWandSnapshot. + * @param snapLhs The snapshot of the left-hand side that should be applied to the magic wand map. + * @return The snapshot of the right-hand side that preserves the values of the left-hand side. + */ + def applyToWandMap(snapLhs: Term): Term = MapLookup(wandMap, snapLhs) +} + +object MagicWandSnapshot { + /** + * Create a new instance of `MagicWandSnapshot`. * - * @param snapshot A MagicWandSnapshot instance or a variable to a Map(Snap, Snap). - * @return The resulting MagicWandSnapshot instance. + * @see [[viper.silicon.state.terms.MagicWandSnapshot]] */ - def apply(snapshot: Term): MagicWandSnapshot = { - snapshot match { - case snap: MagicWandSnapshot => { - assert(snapshot.sort == sorts.Snap, s"Expected snapshot to be of sort Snap, but got ${snapshot.sort}") - snap - } - case wandMap: Var => { - utils.assertSort(wandMap, "wandMap", sorts.Map(sorts.Snap, sorts.Snap)) - new MagicWandSnapshot(wandMap) - } - // TODO: Create a new MagicWandSnapshot instance from a pure Snapshot, i.e. when inhaling a magic wand. - // This map should take First(snapshot) as key and merge them with Second(snapshot) - } - } + def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapshot = new MagicWandSnapshot(abstractLhs, rhsSnapshot, wandMap) - def unapply(wandSnapshot: MagicWandSnapshot): Option[Term] = Some(wandSnapshot.wandMap) + def unapply(snap: MagicWandSnapshot): Option[(Var, Term, Term)] = Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) } class MagicWandChunkTerm(val chunk: MagicWandChunk) extends Term with ConditionalFlyweight[MagicWandChunk, MagicWandChunkTerm] { diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index f2f0b00f6..bfbb0c89b 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -196,7 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) + case MagicWandSnapshot(t0, t1, t2) => MagicWandSnapshot(go(t0), go(t1), go(t2)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) case Second(t) => Second(go(t)) From 90b792e042443d1597294e004c14851a56d4a5a3 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 2 May 2024 17:59:14 +0200 Subject: [PATCH 03/43] Fix broken links in comments. --- .../scala/rules/QuantifiedChunkSupport.scala | 1 - src/main/scala/state/Terms.scala | 20 +++++++++---------- src/main/scala/state/Utils.scala | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 166a33a87..e4fa70429 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -634,7 +634,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { /* Snapshots */ - /** @inheritdoc */ def singletonSnapshotMap(s: State, resource: ast.Resource, arguments: Seq[Term], diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 1ffa9a89a..83c31ac14 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -321,39 +321,39 @@ sealed trait Term extends Node { lazy val subterms: Seq[Term] = state.utils.subterms(this) - /** @see [[ast.utility.Visitor.visit()]] */ + /** @see [[ast.utility.Visitor.visit]] */ def visit(f: PartialFunction[Term, Any]): Unit = ast.utility.Visitor.visit(this, state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.visitOpt()]] */ + /** @see [[ast.utility.Visitor.visitOpt]] */ def visitOpt(f: Term => Boolean): Unit = ast.utility.Visitor.visitOpt(this, state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.reduceTree()]] */ + /** @see [[ast.utility.Visitor.reduceTree]] */ def reduceTree[R](f: (Term, Seq[R]) => R): R = ast.utility.Visitor.reduceTree(this, state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.existsDefined()]] */ + /** @see [[ast.utility.Visitor.existsDefined]] */ def existsDefined(f: PartialFunction[Term, Any]): Boolean = ast.utility.Visitor.existsDefined(this, state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.hasSubnode()]] */ + /** @see [[ast.utility.Visitor.hasSubnode]] */ def hasSubterm(subterm: Term): Boolean = ast.utility.Visitor.hasSubnode(this, subterm, state.utils.subterms) - /** @see [[ast.utility.Visitor.deepCollect()]] */ + /** @see [[ast.utility.Visitor.deepCollect]] */ def deepCollect[R](f: PartialFunction[Term, R]) : Seq[R] = ast.utility.Visitor.deepCollect(Seq(this), state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.shallowCollect()]] */ + /** @see [[ast.utility.Visitor.shallowCollect]] */ def shallowCollect[R](f: PartialFunction[Term, R]): Seq[R] = ast.utility.Visitor.shallowCollect(Seq(this), state.utils.subterms)(f) - /** @see [[ast.utility.Visitor.find()]] */ + /** @see [[ast.utility.Visitor.find]] */ def find[R](f: PartialFunction[Term, R]): Option[R] = ast.utility.Visitor.find(this, state.utils.subterms)(f) - /** @see [[state.utils.transform()]] */ + /** @see [[state.utils.transform]] */ def transform(pre: PartialFunction[Term, Term] = PartialFunction.empty) (recursive: Term => Boolean = !pre.isDefinedAt(_), post: PartialFunction[Term, Term] = PartialFunction.empty) @@ -2470,7 +2470,7 @@ object PsfTop extends (String => Identifier) { */ /* Note: Sort wrappers should probably not be used as (outermost) triggers - * because they are optimised away if wrappee `t` already has sort `to`. + * because they are optimised away if wrapped `t` already has sort `to`. */ class SortWrapper(val t: Term, val to: Sort) extends Term diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index bfbb0c89b..a0575ed5a 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -94,7 +94,7 @@ package object utils { } - /** @see [[viper.silver.ast.utility.Transformer.simplify()]] */ + /** @see [[viper.silver.ast.utility.Simplifier.simplify]] */ def transform[T <: Term](term: T, pre: PartialFunction[Term, Term] = PartialFunction.empty) (recursive: Term => Boolean = !pre.isDefinedAt(_), From 77324f4c332b7d178a33b9b6facf9efaeb5d9729 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Mon, 6 May 2024 10:00:14 +0200 Subject: [PATCH 04/43] First optimization: Create sort MagicWandSnapFunction (MWSF) with its own function definitions to replace wand maps. --- ...agic_wand_snap_functions_declarations.smt2 | 1 + .../decider/TermToSMTLib2Converter.scala | 6 +- src/main/scala/rules/MagicWandSupporter.scala | 4 +- src/main/scala/rules/Producer.scala | 6 +- src/main/scala/state/Terms.scala | 35 +++++-- src/main/scala/state/Utils.scala | 1 + .../supporters/DefaultMapsContributor.scala | 11 --- .../supporters/DefaultSetsContributor.scala | 5 - .../MagicWandSnapFunctionsContributor.scala | 92 +++++++++++++++++++ .../scala/verifier/DefaultMainVerifier.scala | 8 ++ 10 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 src/main/resources/magic_wand_snap_functions_declarations.smt2 create mode 100644 src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala diff --git a/src/main/resources/magic_wand_snap_functions_declarations.smt2 b/src/main/resources/magic_wand_snap_functions_declarations.smt2 new file mode 100644 index 000000000..988585407 --- /dev/null +++ b/src/main/resources/magic_wand_snap_functions_declarations.smt2 @@ -0,0 +1 @@ +(declare-fun MWSF_apply ($MWSF $Snap) $Snap) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index fe4920a8e..c2c90f497 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -61,6 +61,8 @@ class TermToSMTLib2Converter case sorts.FieldPermFunction() => text("$FPM") case sorts.PredicatePermFunction() => text("$PPM") + + case sorts.MagicWandSnapFunction => text("$MWSF") } def convert(d: Decl): String = { @@ -314,8 +316,8 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(_, _, wandMap) => - render(wandMap) + case MagicWandSnapshot(_, _, wandMap) => render(wandMap) + case MWSFLookup(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm | _: Quantification => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 2259752b6..70ed60efc 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -260,7 +260,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create WandMap for wand $wand") - val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap))) + val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) v3.decider.assumeDefinition(wandSnapshot.definition) // If the wand is part of a quantified expression @@ -292,7 +292,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body case p => p }), - Trigger(MapLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), + Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), ) appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutAbstractLhs, v4) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 08a59ee93..16cef52aa 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,14 +397,14 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snapRhs = sf(sorts.Map(sorts.Snap, sorts.Snap), v) + val snapRhs = sf(sorts.MagicWandSnapFunction, v) // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") val abstractLhs = v.decider.fresh(sorts.Snap) - val wandMap = v.decider.fresh("$wm", sorts.Map(sorts.Snap, sorts.Snap)) - val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MapLookup(snapRhs, abstractLhs), wandMap) + val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) + val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, // is the same as our newly created wandMap. diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 83c31ac14..3d858a711 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -76,6 +76,11 @@ object sorts { override lazy val toString = id.toString } + object MagicWandSnapFunction extends Sort { + val id: Identifier = Identifier("MWSF") + override lazy val toString: String = id.toString + } + case class FieldPermFunction() extends Sort { val id = Identifier("FPM") override lazy val toString = id.toString @@ -2308,11 +2313,11 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) - utils.assertSort(wandMap, "wand map", sorts.Map(sorts.Snap, sorts.Snap)) + utils.assertSort(wandMap, "wand map", sorts.MagicWandSnapFunction) - override val sort: Sort = sorts.Map(sorts.Snap, sorts.Snap) + override val sort: Sort = sorts.MagicWandSnapFunction - override lazy val toString = s"wandSnap(wandMap = $wandMap)" + override lazy val toString = s"wandSnap($wandMap)" override val equalityDefiningMembers: Term = wandMap @@ -2323,8 +2328,8 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap */ def definition: Term = Forall( abstractLhs, - MapLookup(wandMap, abstractLhs) === rhsSnapshot, - Trigger(MapLookup(wandMap, abstractLhs)) + MWSFLookup(wandMap, abstractLhs) === rhsSnapshot, + Trigger(MWSFLookup(wandMap, abstractLhs)) ) /** @@ -2334,7 +2339,7 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap * @param snapLhs The snapshot of the left-hand side that should be applied to the magic wand map. * @return The snapshot of the right-hand side that preserves the values of the left-hand side. */ - def applyToWandMap(snapLhs: Term): Term = MapLookup(wandMap, snapLhs) + def applyToWandMap(snapLhs: Term): Term = MWSFLookup(wandMap, snapLhs) } object MagicWandSnapshot { @@ -2348,6 +2353,24 @@ object MagicWandSnapshot { def unapply(snap: MagicWandSnapshot): Option[(Var, Term, Term)] = Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) } +class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { + val sort: Sort = sorts.Snap + override def p0: Term = mwsf + override def p1: Term = snap + override lazy val toString = s"$mwsf[$snap]" +} + +object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { + override def apply(pair: (Term, Term)) = { + val (mwsf, snap) = pair + utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) + utils.assertSort(snap, "snap", sorts.Snap) + createIfNonExistent(pair) + } + + override def actualCreate(args: (Term, Term)): MWSFLookup = new MWSFLookup(args._1, args._2) +} + class MagicWandChunkTerm(val chunk: MagicWandChunk) extends Term with ConditionalFlyweight[MagicWandChunk, MagicWandChunkTerm] { override val sort = sorts.Unit /* TODO: Does this make sense? */ override lazy val toString = s"wand@${chunk.id.ghostFreeWand.pos}}" diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index a0575ed5a..e37aea1fd 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -197,6 +197,7 @@ package object utils { case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) case MagicWandSnapshot(t0, t1, t2) => MagicWandSnapshot(go(t0), go(t1), go(t2)) + case MWSFLookup(t0, t1) => MWSFLookup(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) case Second(t) => Second(go(t)) diff --git a/src/main/scala/supporters/DefaultMapsContributor.scala b/src/main/scala/supporters/DefaultMapsContributor.scala index 606d5196b..0f2317e91 100644 --- a/src/main/scala/supporters/DefaultMapsContributor.scala +++ b/src/main/scala/supporters/DefaultMapsContributor.scala @@ -29,15 +29,4 @@ class DefaultMapsContributor(val domainTranslator: DomainsTranslator[Term], conf assert(argumentSorts.size == 2) sorts.Map(argumentSorts.head, argumentSorts.tail.head) } - - override def computeGroundTypeInstances(program: ast.Program): InsertionOrderedSet[BuiltinDomainType] = { - var setTypeInstances = super.computeGroundTypeInstances(program) - - // Packaging and applying magic wands depends on Maps of type [[viper.silicon.state.terms.sorts.Snap]] - if (program.existsDefined { case ast.MagicWand(_, _) => true }) { - setTypeInstances ++= InsertionOrderedSet(Set(ast.MapType(ViperEmbedding(sorts.Snap), ViperEmbedding(sorts.Snap)))) - } - - setTypeInstances - } } diff --git a/src/main/scala/supporters/DefaultSetsContributor.scala b/src/main/scala/supporters/DefaultSetsContributor.scala index 0fe8f276f..59058d7e4 100644 --- a/src/main/scala/supporters/DefaultSetsContributor.scala +++ b/src/main/scala/supporters/DefaultSetsContributor.scala @@ -67,11 +67,6 @@ class DefaultSetsContributor(val domainTranslator: DomainsTranslator[Term], conf case ast.MapType(keyType, valueType) => Set(ast.SetType(keyType), ast.SetType(valueType)) }.flatten - // Packaging and applying magic wands depends on Sets of type [[viper.silicon.state.terms.sorts.Snap]] - if (program.existsDefined { case ast.MagicWand(_, _) => true }) { - setTypeInstances ++= InsertionOrderedSet(Set(ast.SetType(ViperEmbedding(sorts.Snap)))) - } - setTypeInstances } diff --git a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala new file mode 100644 index 000000000..171418206 --- /dev/null +++ b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala @@ -0,0 +1,92 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2024 ETH Zurich. + +package viper.silicion.supporters + +import viper.silicon.common.collections.immutable.InsertionOrderedSet +import viper.silicon.interfaces.{PreambleContributor, PreambleReader} +import viper.silicon.interfaces.decider.ProverLike +import viper.silicon.state.terms.{Sort, SortDecl, sorts} +import viper.silver.ast +import viper.silver.ast.Program + +/** + * Add function declarations when the proof makes use of MagicWandSnapFunctions (MWSF). + * Those are used to preserve values across multiple applications of a magic wand, e.g. by using an applying expression. + * + * @param preambleReader Reader that returns the content of some given SMT2 files. + */ +class MagicWandSnapFunctionsContributor(preambleReader: PreambleReader[String, String]) + extends PreambleContributor[Sort, String, String] { + + /** File which contains all function declarations in the SMT2 syntax. */ + private val FILE_DECLARATIONS = "/magic_wand_snap_functions_declarations.smt2" + + /** Set for the sort [[viper.silicon.state.terms.sorts.MagicWandSnapFunction]] */ + private var collectedSorts: InsertionOrderedSet[Sort] = InsertionOrderedSet.empty + + /** Set for all function declaration related to [[viper.silicon.state.terms.sorts.MagicWandSnapFunction]] */ + private var collectedFunctionDecls: Iterable[String] = Seq.empty + + /* Functionality */ + + /** + * Add all function declarations when a program contains magic wands. + * + * @param program AST of the program to prove. + */ + override def analyze(program: Program): Unit = { + // If there are not magic wands, do not add any definitions or axioms + if (!program.existsDefined { case ast.MagicWand(_, _) => true }) return + + collectedSorts = InsertionOrderedSet(sorts.MagicWandSnapFunction) + collectedFunctionDecls = preambleReader.readPreamble(FILE_DECLARATIONS) + } + + /** Sorts to add to the preamble of the SMT proof script. */ + override def sortsAfterAnalysis: Iterable[Sort] = collectedSorts + + /** Add all sorts needed to the preamble using `sink`. */ + override def declareSortsAfterAnalysis(sink: ProverLike): Unit = { + sortsAfterAnalysis foreach (s => sink.declare(SortDecl(s))) + } + + /** Symbols to add to the preamble of the SMT proof script. */ + override def symbolsAfterAnalysis: Iterable[String] = + extractPreambleLines(collectedFunctionDecls) + + /** Add all symbols needed to the preamble using `sink`. */ + override def declareSymbolsAfterAnalysis(sink: ProverLike): Unit = + emitPreambleLines(sink, collectedFunctionDecls) + + /** Axioms to add to the preamble of the SMT proof script. Currently, there are none. */ + override def axiomsAfterAnalysis: Iterable[String] = Seq.empty + + /** Add all axioms needed to the preamble using `sink`. Currently, there are none. */ + override def emitAxiomsAfterAnalysis(sink: ProverLike): Unit = {} + + /** Helper function to transform the lines returned by the `PreambleReader`. */ + private def extractPreambleLines(lines: Iterable[String]*): Iterable[String] = + lines.flatten + + /** Helper function to emit all lines using `sink`. */ + private def emitPreambleLines(sink: ProverLike, lines: Iterable[String]*): Unit = { + lines foreach { declaration => + sink.emit(declaration) + } + } + + /* Lifetime */ + + def reset(): Unit = { + collectedSorts = InsertionOrderedSet.empty + collectedFunctionDecls = Seq.empty + } + + def stop(): Unit = {} + + def start(): Unit = {} +} diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 61c4e31f5..667da6cf6 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,6 +6,7 @@ package viper.silicon.verifier +import viper.silicion.supporters.MagicWandSnapFunctionsContributor import viper.silicon.Config.{ExhaleMode, JoinMode} import java.text.SimpleDateFormat @@ -73,6 +74,7 @@ class DefaultMainVerifier(config: Config, protected val fieldValueFunctionsContributor = new DefaultFieldValueFunctionsContributor(preambleReader, symbolConverter, termConverter, config) protected val predSnapGenerator = new PredicateSnapGenerator(symbolConverter, snapshotSupporter) protected val predicateAndWandSnapFunctionsContributor = new DefaultPredicateAndWandSnapFunctionsContributor(preambleReader, termConverter, predSnapGenerator, config) + protected val magicWandSnapFunctionsContributor = new MagicWandSnapFunctionsContributor(preambleReader) private val _verificationPoolManager: VerificationPoolManager = new VerificationPoolManager(this) def verificationPoolManager: VerificationPoolManager = _verificationPoolManager @@ -82,6 +84,7 @@ class DefaultMainVerifier(config: Config, sequencesContributor, setsContributor, multisetsContributor, mapsContributor, domainsContributor, fieldValueFunctionsContributor, predSnapGenerator, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter, _verificationPoolManager, MultiRunRecorders /* In lieu of a better place, include MultiRunRecorders singleton here */ @@ -459,6 +462,7 @@ class DefaultMainVerifier(config: Config, domainsContributor, fieldValueFunctionsContributor, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter ) @@ -471,6 +475,7 @@ class DefaultMainVerifier(config: Config, domainsContributor, fieldValueFunctionsContributor, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter ) @@ -483,6 +488,7 @@ class DefaultMainVerifier(config: Config, domainsContributor, fieldValueFunctionsContributor, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter ) @@ -500,6 +506,7 @@ class DefaultMainVerifier(config: Config, domainsContributor, fieldValueFunctionsContributor, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter ) @@ -512,6 +519,7 @@ class DefaultMainVerifier(config: Config, domainsContributor, fieldValueFunctionsContributor, predicateAndWandSnapFunctionsContributor, + magicWandSnapFunctionsContributor, functionsSupporter, predicateSupporter ) From fd87482c7f9af2d31f32add3ba77c221917640b7 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Mon, 6 May 2024 11:48:12 +0200 Subject: [PATCH 05/43] Second optimization: When there is no applying expression use the original approach using MagicWandSnapSingleton. --- .../decider/TermToSMTLib2Converter.scala | 3 +- .../scala/decider/TermToZ3APIConverter.scala | 2 + src/main/scala/rules/MagicWandSupporter.scala | 57 ++++++++----- src/main/scala/rules/Producer.scala | 31 ++++--- src/main/scala/state/Terms.scala | 84 ++++++++++++++++--- src/main/scala/state/Utils.scala | 3 +- 6 files changed, 136 insertions(+), 44 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index c2c90f497..96c4e961b 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -316,7 +316,8 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(_, _, wandMap) => render(wandMap) + case snap: MagicWandSnapSingleton => renderBinaryOp("$Snap.combine", snap) + case MagicWandSnapFunction(_, _, wandMap) => render(wandMap) case MWSFLookup(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm diff --git a/src/main/scala/decider/TermToZ3APIConverter.scala b/src/main/scala/decider/TermToZ3APIConverter.scala index 3766ee49f..821784ab4 100644 --- a/src/main/scala/decider/TermToZ3APIConverter.scala +++ b/src/main/scala/decider/TermToZ3APIConverter.scala @@ -435,6 +435,8 @@ class TermToZ3APIConverter case bop: Combine => ctx.mkApp(combineConstructor, convertTerm(bop.p0), convertTerm(bop.p1)) + case MagicWandSnapSingleton(abstractLhs, rhsSnapshot) => + ctx.mkApp(combineConstructor, convertTerm(abstractLhs), convertTerm(rhsSnapshot)) case SortWrapper(t, to) => createApp(convertId(SortWrapperId(t.sort, to)), Seq(t), to) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 70ed60efc..3dc88ca5e 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silver.ast -import viper.silver.ast.{Exp, Stmt} +import viper.silver.ast.{Applying, Exp, Program, Stmt} import viper.silver.cfg.Edge import viper.silver.cfg.silver.SilverCfg.SilverBlock import viper.silver.verifier.PartialVerificationError @@ -16,7 +16,7 @@ import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ -import viper.silicon.state.terms.{MagicWandSnapshot, _} +import viper.silicon.state.terms.{MagicWandSnapshot, MagicWandSnapSingleton, MagicWandSnapFunction, _} import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier @@ -212,10 +212,6 @@ object magicWandSupporter extends SymbolicExecutionRules { val s = if (state.exhaleExt) state else state.copy(reserveHeaps = Heap() :: state.h :: Nil) - // v.logger.debug(s"wand = $wand") - // v.logger.debug("c.reserveHeaps:") - // s.reserveHeaps.map(v.stateFormatter.format).foreach(str => v.logger.debug(str, 2)) - val stackSize = 3 + s.reserveHeaps.tail.size // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below var recordedBranches: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[Term], Chunk)] = Nil @@ -260,8 +256,13 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create WandMap for wand $wand") - val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) - v3.decider.assumeDefinition(wandSnapshot.definition) + val wandSnapshot: MagicWandSnapshot = if (useMWSF(s4.program)) { + val fct = MagicWandSnapFunction(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) + v3.decider.assumeDefinition(fct.definition) + fct + } else { + MagicWandSnapSingleton(freshSnapRoot, snapRhs) + } // If the wand is part of a quantified expression if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -285,16 +286,18 @@ object magicWandSupporter extends SymbolicExecutionRules { // Partition path conditions into a set which include the abstractLhs and those which do not val (pcsWithAbstractLhs, pcsWithoutAbstractLhs) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(wandSnapshot.abstractLhs)) // For all path conditions which include the abstractLhs, add those as part of the definition of the wandMap in the same forall quantifier - val pcsQuantified = Forall( - wandSnapshot.abstractLhs, - And(pcsWithAbstractLhs.map { - // Remove redundant forall quantifiers with the same quantified variable - case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body - case p => p - }), - Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), - ) - + val pcsQuantified = wandSnapshot match { + case _: MagicWandSnapSingleton => And(pcsWithAbstractLhs) + case wandSnapshot: MagicWandSnapFunction => Forall( + wandSnapshot.abstractLhs, + And(pcsWithAbstractLhs.map { + // Remove redundant forall quantifiers with the same quantified variable + case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body + case p => p + }), + Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), + ) + } appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutAbstractLhs, v4) Success() }) @@ -418,8 +421,11 @@ object magicWandSupporter extends SymbolicExecutionRules { // If the snapWand is a (wrapped) MagicWandSnapshot then lookup the snapshot of the right-hand side by applying snapLhs. val magicWandSnapshotLookup = snapWand match { - case snapshot: MagicWandSnapshot => snapshot.applyToWandMap(snapLhs) - case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToWandMap(snapLhs) + case snapshot: MagicWandSnapSingleton => + v2.decider.assume(snapLhs === snapshot.abstractLhs) + snapshot.rhsSnapshot + case snapshot: MagicWandSnapFunction => snapshot.applyToWandMap(snapLhs) + case SortWrapper(snapshot: MagicWandSnapFunction, _) => snapshot.applyToWandMap(snapLhs) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => v2.decider.assume(snapLhs === First(snapWand)) @@ -524,4 +530,15 @@ object magicWandSupporter extends SymbolicExecutionRules { s.reserveCfgs.head.outEdges(b) else s.methodCfg.outEdges(b) + + /** + * Determine, whether to use MWSF or MagicWandSnapshot. + * MWSF have the advantage over MagicWandSnapshot that they can be applied multiple times. + * However, they are slower than MagicWandSnapshots. + * + * @param program AST program which is verified. + * @return Boolean indicating whether to use MWSF instead of MagicWandSnapshot. + */ + def useMWSF(program: Program): Boolean = + program.existsDefined { case Applying(_, _) => true } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 16cef52aa..9acca8877 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,18 +397,25 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snapRhs = sf(sorts.MagicWandSnapFunction, v) - - // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, - // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. - v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") - val abstractLhs = v.decider.fresh(sorts.Snap) - val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) - val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) - - // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, - // is the same as our newly created wandMap. - v.decider.assumeDefinition(magicWandSnapshot.definition) + val magicWandSnapshot: MagicWandSnapshot = if (magicWandSupporter.useMWSF(s.program)) { + val snapRhs = sf(sorts.MagicWandSnapFunction, v) + + // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, + // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. + v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") + val abstractLhs = v.decider.fresh(sorts.Snap) + val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) + val snapFunction = MagicWandSnapFunction(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) + + // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, + // is the same as our newly created wandMap. + v.decider.assumeDefinition(snapFunction.definition) + snapFunction + + } else { + val snap = sf(sorts.Snap, v) + MagicWandSnapSingleton(snap) + } magicWandSupporter.createChunk(s, wand, magicWandSnapshot, pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 3d858a711..811fa4b46 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2300,6 +2300,68 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T /* Magic wands */ +abstract class MagicWandSnapshot(val abstractLhs: Term, val rhsSnapshot: Term) extends Term { + utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) + utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) +} + +/** + * Snapshot for a magic wand. It represents a function which can be used only once + * by equating a snapshot from consuming the wand's LHS with `abstractLhs`. + * + * For a snapshot which can be applied multiple times see [[MagicWandSnapFunction]]. + * + * @param abstractLhs The abstract snapshot which acts as a placeholder in the + * `rhsSnapshot` for the values when applying the magic wand. + * @param rhsSnapshot Snapshot which can be used when producing the wand's RHS. + */ +class MagicWandSnapSingleton(override val abstractLhs: Term, override val rhsSnapshot: Term) + extends MagicWandSnapshot(abstractLhs, rhsSnapshot) + with ConditionalFlyweightBinaryOp[MagicWandSnapSingleton] { + + override lazy val toString: String = s"wandSnap(lhs = $abstractLhs, rhs = $rhsSnapshot)" + + /* ConditionalFlyweightBinaryOp members */ + + override def sort: Sort = sorts.Snap + + override def p0: Term = abstractLhs + + override def p1: Term = rhsSnapshot +} + +object MagicWandSnapSingleton { + var pool = new TrieMap[(Term, Term), MagicWandSnapSingleton]() + + /** Craete a new [[MagicWandSnapSingleton]] from a single snapshot term. */ + def apply(snapshot: Term): MagicWandSnapshot = { + assert(snapshot.sort == sorts.Snap, s"MagicWandSnapshot.apply expects sorts Snap but got ${snapshot.sort}") + + snapshot match { + case snap: MagicWandSnapSingleton => snap + case _ => MagicWandSnapSingleton(First(snapshot), Second(snapshot)) + } + } + + /** Create a new [[MagicWandSnapSingleton]] from two snapshot terms. */ + def apply(abstractLhs: Term, rhsSnapshot: Term): MagicWandSnapSingleton = + createIfNonExistent((abstractLhs, rhsSnapshot)) + + /** Destruct a [[MagicWandSnapSingleton]] instance. Used in [[viper.silicon.state.utils.transform]] */ + def unapply(mws: MagicWandSnapSingleton): Some[(Term, Term)] = + Some((mws.abstractLhs, mws.rhsSnapshot)) + + private def createIfNonExistent(args: (Term, Term)): MagicWandSnapSingleton = { + if (Verifier.config.useFlyweight) { + pool.getOrElseUpdate(args, actualCreate(args)) + } else { + actualCreate(args) + } + } + + private def actualCreate(tuple: (Term, Term)) = new MagicWandSnapSingleton(tuple._1, tuple._2) +} + /** * Represents a snapshot of a magic wand, which is a map from Snap to Snap. * @@ -2310,7 +2372,10 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * In the symbolic execution this represents a function that when we apply a magic wand * it consumes the left-hand side and uses the resulting snapshot to look up which right-hand side should be produced. */ -class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { +class MagicWandSnapFunction(override val abstractLhs: Var, override val rhsSnapshot: Term, val wandMap: Term) + extends MagicWandSnapshot(abstractLhs, rhsSnapshot) + with ConditionalFlyweight[Term, MagicWandSnapFunction] { + utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) utils.assertSort(wandMap, "wand map", sorts.MagicWandSnapFunction) @@ -2342,15 +2407,14 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap def applyToWandMap(snapLhs: Term): Term = MWSFLookup(wandMap, snapLhs) } -object MagicWandSnapshot { - /** - * Create a new instance of `MagicWandSnapshot`. - * - * @see [[viper.silicon.state.terms.MagicWandSnapshot]] - */ - def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapshot = new MagicWandSnapshot(abstractLhs, rhsSnapshot, wandMap) +object MagicWandSnapFunction { + /** Create a new instance of [[viper.silicon.state.terms.MagicWandSnapFunction]]. */ + def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapFunction = + new MagicWandSnapFunction(abstractLhs, rhsSnapshot, wandMap) - def unapply(snap: MagicWandSnapshot): Option[(Var, Term, Term)] = Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) + /** Destructs an instance of [[viper.silicon.state.terms.MagicWandSnapFunction]]. */ + def unapply(snap: MagicWandSnapFunction): Option[(Var, Term, Term)] = + Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) } class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { @@ -2361,7 +2425,7 @@ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFl } object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { - override def apply(pair: (Term, Term)) = { + override def apply(pair: (Term, Term)): MWSFLookup = { val (mwsf, snap) = pair utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) utils.assertSort(snap, "snap", sorts.Snap) diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index e37aea1fd..5f01ec793 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -196,7 +196,8 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(t0, t1, t2) => MagicWandSnapshot(go(t0), go(t1), go(t2)) + case MagicWandSnapSingleton(t0, t1) => MagicWandSnapSingleton(go(t0), go(t1)) + case MagicWandSnapFunction(t0, t1, t2) => MagicWandSnapFunction(go(t0), go(t1), go(t2)) case MWSFLookup(t0, t1) => MWSFLookup(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) From 45f215c263162e4cbcfd7044a93a6249d87b5d6b Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 8 May 2024 08:31:03 +0200 Subject: [PATCH 06/43] Revert "Second optimization: When there is no applying expression use the original approach using MagicWandSnapSingleton." This reverts commit fd87482c7f9af2d31f32add3ba77c221917640b7. --- .../decider/TermToSMTLib2Converter.scala | 3 +- .../scala/decider/TermToZ3APIConverter.scala | 2 - src/main/scala/rules/MagicWandSupporter.scala | 57 +++++-------- src/main/scala/rules/Producer.scala | 31 +++---- src/main/scala/state/Terms.scala | 84 +++---------------- src/main/scala/state/Utils.scala | 3 +- 6 files changed, 44 insertions(+), 136 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index 96c4e961b..c2c90f497 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -316,8 +316,7 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case snap: MagicWandSnapSingleton => renderBinaryOp("$Snap.combine", snap) - case MagicWandSnapFunction(_, _, wandMap) => render(wandMap) + case MagicWandSnapshot(_, _, wandMap) => render(wandMap) case MWSFLookup(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm diff --git a/src/main/scala/decider/TermToZ3APIConverter.scala b/src/main/scala/decider/TermToZ3APIConverter.scala index 821784ab4..3766ee49f 100644 --- a/src/main/scala/decider/TermToZ3APIConverter.scala +++ b/src/main/scala/decider/TermToZ3APIConverter.scala @@ -435,8 +435,6 @@ class TermToZ3APIConverter case bop: Combine => ctx.mkApp(combineConstructor, convertTerm(bop.p0), convertTerm(bop.p1)) - case MagicWandSnapSingleton(abstractLhs, rhsSnapshot) => - ctx.mkApp(combineConstructor, convertTerm(abstractLhs), convertTerm(rhsSnapshot)) case SortWrapper(t, to) => createApp(convertId(SortWrapperId(t.sort, to)), Seq(t), to) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 3dc88ca5e..70ed60efc 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -7,7 +7,7 @@ package viper.silicon.rules import viper.silver.ast -import viper.silver.ast.{Applying, Exp, Program, Stmt} +import viper.silver.ast.{Exp, Stmt} import viper.silver.cfg.Edge import viper.silver.cfg.silver.SilverCfg.SilverBlock import viper.silver.verifier.PartialVerificationError @@ -16,7 +16,7 @@ import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ -import viper.silicon.state.terms.{MagicWandSnapshot, MagicWandSnapSingleton, MagicWandSnapFunction, _} +import viper.silicon.state.terms.{MagicWandSnapshot, _} import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier @@ -212,6 +212,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val s = if (state.exhaleExt) state else state.copy(reserveHeaps = Heap() :: state.h :: Nil) + // v.logger.debug(s"wand = $wand") + // v.logger.debug("c.reserveHeaps:") + // s.reserveHeaps.map(v.stateFormatter.format).foreach(str => v.logger.debug(str, 2)) + val stackSize = 3 + s.reserveHeaps.tail.size // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below var recordedBranches: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[Term], Chunk)] = Nil @@ -256,13 +260,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create WandMap for wand $wand") - val wandSnapshot: MagicWandSnapshot = if (useMWSF(s4.program)) { - val fct = MagicWandSnapFunction(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) - v3.decider.assumeDefinition(fct.definition) - fct - } else { - MagicWandSnapSingleton(freshSnapRoot, snapRhs) - } + val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) + v3.decider.assumeDefinition(wandSnapshot.definition) // If the wand is part of a quantified expression if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -286,18 +285,16 @@ object magicWandSupporter extends SymbolicExecutionRules { // Partition path conditions into a set which include the abstractLhs and those which do not val (pcsWithAbstractLhs, pcsWithoutAbstractLhs) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(wandSnapshot.abstractLhs)) // For all path conditions which include the abstractLhs, add those as part of the definition of the wandMap in the same forall quantifier - val pcsQuantified = wandSnapshot match { - case _: MagicWandSnapSingleton => And(pcsWithAbstractLhs) - case wandSnapshot: MagicWandSnapFunction => Forall( - wandSnapshot.abstractLhs, - And(pcsWithAbstractLhs.map { - // Remove redundant forall quantifiers with the same quantified variable - case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body - case p => p - }), - Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), - ) - } + val pcsQuantified = Forall( + wandSnapshot.abstractLhs, + And(pcsWithAbstractLhs.map { + // Remove redundant forall quantifiers with the same quantified variable + case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body + case p => p + }), + Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), + ) + appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutAbstractLhs, v4) Success() }) @@ -421,11 +418,8 @@ object magicWandSupporter extends SymbolicExecutionRules { // If the snapWand is a (wrapped) MagicWandSnapshot then lookup the snapshot of the right-hand side by applying snapLhs. val magicWandSnapshotLookup = snapWand match { - case snapshot: MagicWandSnapSingleton => - v2.decider.assume(snapLhs === snapshot.abstractLhs) - snapshot.rhsSnapshot - case snapshot: MagicWandSnapFunction => snapshot.applyToWandMap(snapLhs) - case SortWrapper(snapshot: MagicWandSnapFunction, _) => snapshot.applyToWandMap(snapLhs) + case snapshot: MagicWandSnapshot => snapshot.applyToWandMap(snapLhs) + case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToWandMap(snapLhs) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => v2.decider.assume(snapLhs === First(snapWand)) @@ -530,15 +524,4 @@ object magicWandSupporter extends SymbolicExecutionRules { s.reserveCfgs.head.outEdges(b) else s.methodCfg.outEdges(b) - - /** - * Determine, whether to use MWSF or MagicWandSnapshot. - * MWSF have the advantage over MagicWandSnapshot that they can be applied multiple times. - * However, they are slower than MagicWandSnapshots. - * - * @param program AST program which is verified. - * @return Boolean indicating whether to use MWSF instead of MagicWandSnapshot. - */ - def useMWSF(program: Program): Boolean = - program.existsDefined { case Applying(_, _) => true } } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 9acca8877..16cef52aa 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,25 +397,18 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val magicWandSnapshot: MagicWandSnapshot = if (magicWandSupporter.useMWSF(s.program)) { - val snapRhs = sf(sorts.MagicWandSnapFunction, v) - - // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, - // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. - v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") - val abstractLhs = v.decider.fresh(sorts.Snap) - val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) - val snapFunction = MagicWandSnapFunction(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) - - // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, - // is the same as our newly created wandMap. - v.decider.assumeDefinition(snapFunction.definition) - snapFunction - - } else { - val snap = sf(sorts.Snap, v) - MagicWandSnapSingleton(snap) - } + val snapRhs = sf(sorts.MagicWandSnapFunction, v) + + // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, + // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. + v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") + val abstractLhs = v.decider.fresh(sorts.Snap) + val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) + val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) + + // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, + // is the same as our newly created wandMap. + v.decider.assumeDefinition(magicWandSnapshot.definition) magicWandSupporter.createChunk(s, wand, magicWandSnapshot, pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 811fa4b46..3d858a711 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2300,68 +2300,6 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T /* Magic wands */ -abstract class MagicWandSnapshot(val abstractLhs: Term, val rhsSnapshot: Term) extends Term { - utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) - utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) -} - -/** - * Snapshot for a magic wand. It represents a function which can be used only once - * by equating a snapshot from consuming the wand's LHS with `abstractLhs`. - * - * For a snapshot which can be applied multiple times see [[MagicWandSnapFunction]]. - * - * @param abstractLhs The abstract snapshot which acts as a placeholder in the - * `rhsSnapshot` for the values when applying the magic wand. - * @param rhsSnapshot Snapshot which can be used when producing the wand's RHS. - */ -class MagicWandSnapSingleton(override val abstractLhs: Term, override val rhsSnapshot: Term) - extends MagicWandSnapshot(abstractLhs, rhsSnapshot) - with ConditionalFlyweightBinaryOp[MagicWandSnapSingleton] { - - override lazy val toString: String = s"wandSnap(lhs = $abstractLhs, rhs = $rhsSnapshot)" - - /* ConditionalFlyweightBinaryOp members */ - - override def sort: Sort = sorts.Snap - - override def p0: Term = abstractLhs - - override def p1: Term = rhsSnapshot -} - -object MagicWandSnapSingleton { - var pool = new TrieMap[(Term, Term), MagicWandSnapSingleton]() - - /** Craete a new [[MagicWandSnapSingleton]] from a single snapshot term. */ - def apply(snapshot: Term): MagicWandSnapshot = { - assert(snapshot.sort == sorts.Snap, s"MagicWandSnapshot.apply expects sorts Snap but got ${snapshot.sort}") - - snapshot match { - case snap: MagicWandSnapSingleton => snap - case _ => MagicWandSnapSingleton(First(snapshot), Second(snapshot)) - } - } - - /** Create a new [[MagicWandSnapSingleton]] from two snapshot terms. */ - def apply(abstractLhs: Term, rhsSnapshot: Term): MagicWandSnapSingleton = - createIfNonExistent((abstractLhs, rhsSnapshot)) - - /** Destruct a [[MagicWandSnapSingleton]] instance. Used in [[viper.silicon.state.utils.transform]] */ - def unapply(mws: MagicWandSnapSingleton): Some[(Term, Term)] = - Some((mws.abstractLhs, mws.rhsSnapshot)) - - private def createIfNonExistent(args: (Term, Term)): MagicWandSnapSingleton = { - if (Verifier.config.useFlyweight) { - pool.getOrElseUpdate(args, actualCreate(args)) - } else { - actualCreate(args) - } - } - - private def actualCreate(tuple: (Term, Term)) = new MagicWandSnapSingleton(tuple._1, tuple._2) -} - /** * Represents a snapshot of a magic wand, which is a map from Snap to Snap. * @@ -2372,10 +2310,7 @@ object MagicWandSnapSingleton { * In the symbolic execution this represents a function that when we apply a magic wand * it consumes the left-hand side and uses the resulting snapshot to look up which right-hand side should be produced. */ -class MagicWandSnapFunction(override val abstractLhs: Var, override val rhsSnapshot: Term, val wandMap: Term) - extends MagicWandSnapshot(abstractLhs, rhsSnapshot) - with ConditionalFlyweight[Term, MagicWandSnapFunction] { - +class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) utils.assertSort(wandMap, "wand map", sorts.MagicWandSnapFunction) @@ -2407,14 +2342,15 @@ class MagicWandSnapFunction(override val abstractLhs: Var, override val rhsSnaps def applyToWandMap(snapLhs: Term): Term = MWSFLookup(wandMap, snapLhs) } -object MagicWandSnapFunction { - /** Create a new instance of [[viper.silicon.state.terms.MagicWandSnapFunction]]. */ - def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapFunction = - new MagicWandSnapFunction(abstractLhs, rhsSnapshot, wandMap) +object MagicWandSnapshot { + /** + * Create a new instance of `MagicWandSnapshot`. + * + * @see [[viper.silicon.state.terms.MagicWandSnapshot]] + */ + def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapshot = new MagicWandSnapshot(abstractLhs, rhsSnapshot, wandMap) - /** Destructs an instance of [[viper.silicon.state.terms.MagicWandSnapFunction]]. */ - def unapply(snap: MagicWandSnapFunction): Option[(Var, Term, Term)] = - Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) + def unapply(snap: MagicWandSnapshot): Option[(Var, Term, Term)] = Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) } class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { @@ -2425,7 +2361,7 @@ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFl } object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { - override def apply(pair: (Term, Term)): MWSFLookup = { + override def apply(pair: (Term, Term)) = { val (mwsf, snap) = pair utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) utils.assertSort(snap, "snap", sorts.Snap) diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index 5f01ec793..e37aea1fd 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -196,8 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapSingleton(t0, t1) => MagicWandSnapSingleton(go(t0), go(t1)) - case MagicWandSnapFunction(t0, t1, t2) => MagicWandSnapFunction(go(t0), go(t1), go(t2)) + case MagicWandSnapshot(t0, t1, t2) => MagicWandSnapshot(go(t0), go(t1), go(t2)) case MWSFLookup(t0, t1) => MWSFLookup(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) From d67aa274243c2ab51f7977f110a7f79500a5b746 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 8 May 2024 09:53:44 +0200 Subject: [PATCH 07/43] Apply suggested changes from code review. --- .../scala/decider/TermToZ3APIConverter.scala | 2 ++ src/main/scala/rules/MagicWandSupporter.scala | 2 +- src/main/scala/state/Terms.scala | 19 +++++++------------ .../supporters/DefaultMapsContributor.scala | 2 -- .../supporters/DefaultSetsContributor.scala | 1 - .../MagicWandSnapFunctionsContributor.scala | 2 +- ...icateAndWandSnapFunctionsContributor.scala | 4 +--- .../scala/verifier/DefaultMainVerifier.scala | 1 - 8 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/main/scala/decider/TermToZ3APIConverter.scala b/src/main/scala/decider/TermToZ3APIConverter.scala index 3766ee49f..152bf1d64 100644 --- a/src/main/scala/decider/TermToZ3APIConverter.scala +++ b/src/main/scala/decider/TermToZ3APIConverter.scala @@ -445,6 +445,8 @@ class TermToZ3APIConverter case Let(bindings, body) => convert(body.replace(bindings)) + case MWSFLookup(mwsf, snap) => createApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) + case _: MagicWandChunkTerm | _: Quantification => sys.error(s"Unexpected term $term cannot be translated to SMTLib code") diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 70ed60efc..cce771065 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -263,7 +263,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) v3.decider.assumeDefinition(wandSnapshot.definition) - // If the wand is part of a quantified expression + // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { val bodyVars = wand.subexpressionsToEvaluate(s.program) val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 3d858a711..3c519ddda 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2310,7 +2310,7 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * In the symbolic execution this represents a function that when we apply a magic wand * it consumes the left-hand side and uses the resulting snapshot to look up which right-hand side should be produced. */ -class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { +class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[(Var, Term, Term), MagicWandSnapshot] { utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) utils.assertSort(wandMap, "wand map", sorts.MagicWandSnapFunction) @@ -2319,7 +2319,7 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap override lazy val toString = s"wandSnap($wandMap)" - override val equalityDefiningMembers: Term = wandMap + override val equalityDefiningMembers: (Var, Term, Term) = (abstractLhs, rhsSnapshot, wandMap) /** * Creates a term that can be used to define a `wandMap`. @@ -2342,15 +2342,10 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap def applyToWandMap(snapLhs: Term): Term = MWSFLookup(wandMap, snapLhs) } -object MagicWandSnapshot { - /** - * Create a new instance of `MagicWandSnapshot`. - * - * @see [[viper.silicon.state.terms.MagicWandSnapshot]] - */ - def apply(abstractLhs: Var, rhsSnapshot: Term, wandMap: Term): MagicWandSnapshot = new MagicWandSnapshot(abstractLhs, rhsSnapshot, wandMap) - - def unapply(snap: MagicWandSnapshot): Option[(Var, Term, Term)] = Some(snap.abstractLhs, snap.rhsSnapshot, snap.wandMap) +object MagicWandSnapshot extends PreciseCondFlyweightFactory[(Var, Term, Term), MagicWandSnapshot] { + /** Create an instance of [[viper.silicon.state.terms.MagicWandSnapshot]]. */ + override def actualCreate(args: (Var, Term, Term)): MagicWandSnapshot = + new MagicWandSnapshot(args._1, args._2, args._3) } class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { @@ -2361,7 +2356,7 @@ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFl } object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { - override def apply(pair: (Term, Term)) = { + override def apply(pair: (Term, Term)): MWSFLookup = { val (mwsf, snap) = pair utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) utils.assertSort(snap, "snap", sorts.Snap) diff --git a/src/main/scala/supporters/DefaultMapsContributor.scala b/src/main/scala/supporters/DefaultMapsContributor.scala index 0f2317e91..f41a5fbaa 100644 --- a/src/main/scala/supporters/DefaultMapsContributor.scala +++ b/src/main/scala/supporters/DefaultMapsContributor.scala @@ -8,10 +8,8 @@ package viper.silicon.supporters import scala.reflect.{ClassTag, classTag} import viper.silicon.Config -import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silver.ast import viper.silicon.state.terms.{Sort, Term, sorts} -import viper.silicon.utils.ast.ViperEmbedding class DefaultMapsContributor(val domainTranslator: DomainsTranslator[Term], config: Config) extends BuiltinDomainsContributor { diff --git a/src/main/scala/supporters/DefaultSetsContributor.scala b/src/main/scala/supporters/DefaultSetsContributor.scala index 59058d7e4..3a14ea459 100644 --- a/src/main/scala/supporters/DefaultSetsContributor.scala +++ b/src/main/scala/supporters/DefaultSetsContributor.scala @@ -12,7 +12,6 @@ import viper.silver.ast import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.state.terms.{Sort, Term, sorts} import viper.silicon.verifier.Verifier -import viper.silicon.utils.ast.ViperEmbedding class DefaultSetsContributor(val domainTranslator: DomainsTranslator[Term], config: Config) extends BuiltinDomainsContributor { diff --git a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala index 171418206..c2b925c6a 100644 --- a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala @@ -4,7 +4,7 @@ // // Copyright (c) 2011-2024 ETH Zurich. -package viper.silicion.supporters +package viper.silicon.supporters import viper.silicon.common.collections.immutable.InsertionOrderedSet import viper.silicon.interfaces.{PreambleContributor, PreambleReader} diff --git a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala index 920405ec6..3da5802d1 100644 --- a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala @@ -21,7 +21,6 @@ import viper.silicon.state.terms.{Sort, SortDecl, sorts} import viper.silver.ast.PredicateAccess trait PredicateSnapFunctionsContributor[SO, SY, AX] extends PreambleContributor[SO, SY, AX] -trait MagicWandSnapFunctionsContributor[SO, SY, AX] extends PreambleContributor[SO, SY, AX] /** Creates background definitions for n-tuples of predicate and wand arguments. Currently, * snapshot trees are used to built n-tuples. @@ -45,8 +44,7 @@ class DefaultPredicateAndWandSnapFunctionsContributor(preambleReader: PreambleRe termConverter: TermConverter[String, String, String], predicateSnapGenerator: PredicateSnapGenerator, config: Config) - extends PredicateSnapFunctionsContributor[Sort, String, String] - with MagicWandSnapFunctionsContributor[Sort, String, String] { + extends PredicateSnapFunctionsContributor[Sort, String, String] { /* PreambleBlock = Comment x Lines */ private type PreambleBlock = (String, Iterable[String]) diff --git a/src/main/scala/verifier/DefaultMainVerifier.scala b/src/main/scala/verifier/DefaultMainVerifier.scala index 667da6cf6..a7d69656d 100644 --- a/src/main/scala/verifier/DefaultMainVerifier.scala +++ b/src/main/scala/verifier/DefaultMainVerifier.scala @@ -6,7 +6,6 @@ package viper.silicon.verifier -import viper.silicion.supporters.MagicWandSnapFunctionsContributor import viper.silicon.Config.{ExhaleMode, JoinMode} import java.text.SimpleDateFormat From 2a887c4889b06deca33bb4407144f5a01c5a5957 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Mon, 13 May 2024 09:26:41 +0200 Subject: [PATCH 08/43] Fix test cases with quasihavoc statements. --- src/main/scala/rules/HavocSupporter.scala | 20 +++++++++++++++---- .../rules/MoreCompleteExhaleSupporter.scala | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index e23472e23..153bcb0fb 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -181,10 +181,22 @@ object havocSupporter extends SymbolicExecutionRules { val id = ChunkIdentifier(resource, s.program) val (relevantChunks, otherChunks) = chunkSupporter.splitHeap[NonQuantifiedChunk](s.h, id) - val newChunks = relevantChunks.map { ch => - val havockedSnap = freshSnap(ch.snap.sort, v) - val cond = replacementCond(lhs, ch.args, condInfo) - ch.withSnap(Ite(cond, havockedSnap, ch.snap)) + val newChunks = relevantChunks.map { + case ch: MagicWandChunk => + val havockedSnap = freshSnap(sorts.Snap, v) + val abstractLhs = freshSnap(sorts.Snap, v) + val freshWandMap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val cond = replacementCond(lhs, ch.args, condInfo) + + // Define a new `MagicWandSnapshot` that checks the havoc condition when being applied + val magicWandSnapshot = MagicWandSnapshot(abstractLhs, Ite(cond, havockedSnap, MWSFLookup(ch.snap.wandMap, abstractLhs)), freshWandMap) + v.decider.assumeDefinition(magicWandSnapshot.definition) + ch.withSnap(magicWandSnapshot) + + case ch => + val havockedSnap = freshSnap(ch.snap.sort, v) + val cond = replacementCond(lhs, ch.args, condInfo) + ch.withSnap(Ite(cond, havockedSnap, ch.snap)) } otherChunks ++ newChunks } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 5c7173c8a..8e983f573 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -55,7 +55,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { resource match { case f: ast.Field => v.symbolConverter.toSort(f.typ) case _: ast.Predicate => sorts.Snap - case _: ast.MagicWand => sorts.Snap + case _: ast.MagicWand => sorts.MagicWandSnapFunction } val `?s` = Var(Identifier("?s"), sort, false) From 2211b6c5bbe3c0b9b02c444fe9862ec0e522a29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 16 May 2024 15:43:21 +0200 Subject: [PATCH 09/43] Update submodule to use branch with both testcase changes --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 8466d7ca8..ca7983460 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 8466d7ca8e3e884ef75679c1e3114445a3be42ae +Subproject commit ca79834608bd2f4660fae0ee7d7b1a3ecabc9a32 From 6195039ef84c8124ef28bc195fd2aeaa338bbced Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Fri, 17 May 2024 08:15:58 +0200 Subject: [PATCH 10/43] Remove abstractLhs and rhsSnapshot from MagicWandSnapshot. --- .../decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 4 +- src/main/scala/rules/MagicWandSupporter.scala | 53 +++++++++++++------ src/main/scala/rules/Producer.scala | 12 ++--- src/main/scala/state/Terms.scala | 51 ++++++++---------- src/main/scala/state/Utils.scala | 2 +- .../BuiltinDomainsContributor.scala | 3 +- 7 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index c2c90f497..6974baea1 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -316,7 +316,7 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(_, _, wandMap) => render(wandMap) + case MagicWandSnapshot(mwsf) => render(mwsf) case MWSFLookup(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 153bcb0fb..920d39dd5 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -185,12 +185,10 @@ object havocSupporter extends SymbolicExecutionRules { case ch: MagicWandChunk => val havockedSnap = freshSnap(sorts.Snap, v) val abstractLhs = freshSnap(sorts.Snap, v) - val freshWandMap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val cond = replacementCond(lhs, ch.args, condInfo) // Define a new `MagicWandSnapshot` that checks the havoc condition when being applied - val magicWandSnapshot = MagicWandSnapshot(abstractLhs, Ite(cond, havockedSnap, MWSFLookup(ch.snap.wandMap, abstractLhs)), freshWandMap) - v.decider.assumeDefinition(magicWandSnapshot.definition) + val magicWandSnapshot = magicWandSupporter.createMagicWandSnapshot(abstractLhs, Ite(cond, havockedSnap, MWSFLookup(ch.snap.mwsf, abstractLhs)), v) ch.withSnap(magicWandSnapshot) case ch => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index cce771065..22c333a8b 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -80,7 +80,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // } /** - * Evaluate the wand's arguments and create a `MagicWandChunk` out of it. + * Evaluate the wand's arguments and create a [[viper.silicon.state.MagicWandChunk]] out of it. */ def createChunk(s: State, wand: ast.MagicWand, @@ -94,6 +94,30 @@ object magicWandSupporter extends SymbolicExecutionRules { ) } + /** + * Create a new [[viper.silicon.state.terms.MagicWandSnapshot]] + * and add the corresponding [[viper.silicon.state.terms.sorts.MagicWandSnapFunction]] definition to the state. + * + * It defines that when we apply the MagicWandSnapFunction to a snapshot `snap` + * we will get back `rhsSnapshot` that includes the values from that snapshot `snap`. + * + * @param abstractLhs The term that represent the snapshot of the consumed left-hand side of the magic wand. + * It is used as a bound variable in a forall quantifier. + * @param rhsSnapshot The term that integrates the left-hand side in the snapshot that is produced after applying the magic wand. + * @param v Verifier instance + * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] + */ + def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val magicWandSnapshot = MagicWandSnapshot(mwsf) + v.decider.assumeDefinition(Forall( + abstractLhs, + MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, + Trigger(MWSFLookup(mwsf, abstractLhs)) + )) + magicWandSnapshot + } + /** * Evaluate all expressions inside the given magic wand instance in the current state. * @@ -259,9 +283,8 @@ object magicWandSupporter extends SymbolicExecutionRules { : VerificationResult = { val preMark = v3.decider.setPathConditionMark() - v3.decider.prover.comment(s"Create WandMap for wand $wand") - val wandSnapshot = MagicWandSnapshot(freshSnapRoot, snapRhs, v3.decider.fresh("mwsf", sorts.MagicWandSnapFunction)) - v3.decider.assumeDefinition(wandSnapshot.definition) + v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") + val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v3) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -282,20 +305,20 @@ object magicWandSupporter extends SymbolicExecutionRules { } else { this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly - // Partition path conditions into a set which include the abstractLhs and those which do not - val (pcsWithAbstractLhs, pcsWithoutAbstractLhs) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(wandSnapshot.abstractLhs)) - // For all path conditions which include the abstractLhs, add those as part of the definition of the wandMap in the same forall quantifier + // Partition path conditions into a set which include the freshSnapRoot and those which do not + val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(freshSnapRoot)) + // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier val pcsQuantified = Forall( - wandSnapshot.abstractLhs, - And(pcsWithAbstractLhs.map { - // Remove redundant forall quantifiers with the same quantified variable - case Quantification(Forall, wandSnapshot.abstractLhs :: Nil, body: Term, _, _, _, _) => body + freshSnapRoot, + And(pcsWithFreshSnapRoot.map { + // Remove forall quantifiers with the same quantified variable + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body case p => p }), - Trigger(MWSFLookup(wandSnapshot.wandMap, wandSnapshot.abstractLhs)), + Trigger(MWSFLookup(wandSnapshot.mwsf, freshSnapRoot)), ) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutAbstractLhs, v4) + appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) Success() }) } @@ -418,8 +441,8 @@ object magicWandSupporter extends SymbolicExecutionRules { // If the snapWand is a (wrapped) MagicWandSnapshot then lookup the snapshot of the right-hand side by applying snapLhs. val magicWandSnapshotLookup = snapWand match { - case snapshot: MagicWandSnapshot => snapshot.applyToWandMap(snapLhs) - case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToWandMap(snapLhs) + case snapshot: MagicWandSnapshot => snapshot.applyToMWSF(snapLhs) + case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => v2.decider.assume(snapLhs === First(snapWand)) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 462be0378..a648d6256 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -399,16 +399,14 @@ object producer extends ProductionRules { case wand: ast.MagicWand => val snapRhs = sf(sorts.MagicWandSnapFunction, v) - // Create Map that takes a snapshot, which represent the values of the consumed LHS of the wand, + // Create MWSF that takes a snapshot, which represent the values of the consumed LHS of the wand, // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. - v.decider.prover.comment(s"Produce new magic wand snapshot map for wand $wand") + v.decider.prover.comment(s"Produce MagicWandSnapFunction for wand $wand") val abstractLhs = v.decider.fresh(sorts.Snap) - val wandMap = v.decider.fresh("$mwsf", sorts.MagicWandSnapFunction) - val magicWandSnapshot = MagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), wandMap) - // We assume that the wandMap that we get from `snapRhs`, which potentially is nested in a binary tree, - // is the same as our newly created wandMap. - v.decider.assumeDefinition(magicWandSnapshot.definition) + // We assume that the MWSF that we get from `snapRhs` is the same as our newly created MWSF. + // `snapRhs` is an expression that potentially returns a nested element of a binary tree. + val magicWandSnapshot = magicWandSupporter.createMagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), v) magicWandSupporter.createChunk(s, wand, magicWandSnapshot, pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 3c519ddda..e08f4854b 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2301,36 +2301,20 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T /* Magic wands */ /** - * Represents a snapshot of a magic wand, which is a map from Snap to Snap. + * Represents a snapshot of a magic wand, which is a function from `Snap` to `Snap`. * - * @param abstractLhs The term that represent the snapshot of the consumed left-hand side of the magic wand. - * It is used as a bound variable in a forall quantifier. - * @param rhsSnapshot The term that integrates the left-hand side in the snapshot that is produced after applying the magic wand. - * @param wandMap The map that represents the snapshot of the magic wand. It is a variable of sort Map(Snap, Snap). - * In the symbolic execution this represents a function that when we apply a magic wand - * it consumes the left-hand side and uses the resulting snapshot to look up which right-hand side should be produced. + * @param mwsf The function that represents the snapshot of the magic wand. It is a variable of sort [[sorts.MagicWandSnapFunction]]. + * In the symbolic execution when we apply a magic wand, it consumes the left-hand side + * and uses this function and the resulting snapshot to look up which right-hand side to produce. */ -class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap: Term) extends Term with ConditionalFlyweight[(Var, Term, Term), MagicWandSnapshot] { - utils.assertSort(abstractLhs, "abstract lhs", sorts.Snap) - utils.assertSort(rhsSnapshot, "rhs snapshot", sorts.Snap) - utils.assertSort(wandMap, "wand map", sorts.MagicWandSnapFunction) +class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { + utils.assertSort(mwsf, "magic wand snap function", sorts.MagicWandSnapFunction) override val sort: Sort = sorts.MagicWandSnapFunction - override lazy val toString = s"wandSnap($wandMap)" + override lazy val toString = s"wandSnap($mwsf)" - override val equalityDefiningMembers: (Var, Term, Term) = (abstractLhs, rhsSnapshot, wandMap) - - /** - * Creates a term that can be used to define a `wandMap`. - * - * @return Expression which says that the map applied to an arbitrary lhs equals to the snapshot of the rhs. - */ - def definition: Term = Forall( - abstractLhs, - MWSFLookup(wandMap, abstractLhs) === rhsSnapshot, - Trigger(MWSFLookup(wandMap, abstractLhs)) - ) + override val equalityDefiningMembers: Term = mwsf /** * Apply the given snapshot of the left-hand side to the magic wand map to get the snapshot of the right-hand side @@ -2339,15 +2323,22 @@ class MagicWandSnapshot(val abstractLhs: Var, val rhsSnapshot: Term, val wandMap * @param snapLhs The snapshot of the left-hand side that should be applied to the magic wand map. * @return The snapshot of the right-hand side that preserves the values of the left-hand side. */ - def applyToWandMap(snapLhs: Term): Term = MWSFLookup(wandMap, snapLhs) + def applyToMWSF(snapLhs: Term): Term = MWSFLookup(mwsf, snapLhs) } -object MagicWandSnapshot extends PreciseCondFlyweightFactory[(Var, Term, Term), MagicWandSnapshot] { +object MagicWandSnapshot extends PreciseCondFlyweightFactory[Term, MagicWandSnapshot] { /** Create an instance of [[viper.silicon.state.terms.MagicWandSnapshot]]. */ - override def actualCreate(args: (Var, Term, Term)): MagicWandSnapshot = - new MagicWandSnapshot(args._1, args._2, args._3) + override def actualCreate(arg: Term): MagicWandSnapshot = + new MagicWandSnapshot(arg) } +/** + * Term that applies a [[sorts.MagicWandSnapFunction]] to a snapshot. + * It returns a snapshot for the RHS of a magic wand that includes that values of the given snapshot. + * + * @param mwsf Term of sort [[sorts.MagicWandSnapFunction]]. Function from `Snap` to `Snap`. + * @param snap Term of sort [[sorts.Snap]] to which the MWSF is applied to. It represents the values of the wand's LHS. + */ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { val sort: Sort = sorts.Snap override def p0: Term = mwsf @@ -2363,7 +2354,9 @@ object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] createIfNonExistent(pair) } - override def actualCreate(args: (Term, Term)): MWSFLookup = new MWSFLookup(args._1, args._2) + /** Create an instance of [[viper.silicon.state.terms.MWSFLookup]]. */ + override def actualCreate(args: (Term, Term)): MWSFLookup = + new MWSFLookup(args._1, args._2) } class MagicWandChunkTerm(val chunk: MagicWandChunk) extends Term with ConditionalFlyweight[MagicWandChunk, MagicWandChunkTerm] { diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index e37aea1fd..d2667e8cd 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -196,7 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(t0, t1, t2) => MagicWandSnapshot(go(t0), go(t1), go(t2)) + case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) case MWSFLookup(t0, t1) => MWSFLookup(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) diff --git a/src/main/scala/supporters/BuiltinDomainsContributor.scala b/src/main/scala/supporters/BuiltinDomainsContributor.scala index 4df6ddadd..bb01d2fd0 100644 --- a/src/main/scala/supporters/BuiltinDomainsContributor.scala +++ b/src/main/scala/supporters/BuiltinDomainsContributor.scala @@ -16,7 +16,6 @@ import viper.silicon.interfaces.PreambleContributor import viper.silicon.interfaces.decider.ProverLike import viper.silicon.state.DefaultSymbolConverter import viper.silicon.state.terms._ -import viper.silver.ast.DomainType abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, DomainFun, Term] { type BuiltinDomainType <: ast.GenericType @@ -82,7 +81,7 @@ abstract class BuiltinDomainsContributor extends PreambleContributor[Sort, Domai /** * For each necessary domain type, instantiate the corresponding domain */ - private def instantiateWithDomain(sourceProgram: ast.Program, sourceDomain: ast.Domain, sourceDomainTypeInstances: InsertionOrderedSet[DomainType]): Set[(ast.DomainType, ast.Domain)] = { + private def instantiateWithDomain(sourceProgram: ast.Program, sourceDomain: ast.Domain, sourceDomainTypeInstances: InsertionOrderedSet[ast.DomainType]): Set[(ast.DomainType, ast.Domain)] = { sourceDomainTypeInstances map (domainType => { /* TODO: Copied from DomainInstances.getInstanceMembers. * Cannot directly use that because it filters according to which domain instances From f306ed78e748470d00843322a11563f5f895fcad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 17 May 2024 10:46:54 +0200 Subject: [PATCH 11/43] Reduce diff --- .../scala/decider/TermToSMTLib2Converter.scala | 17 ++++++++--------- src/main/scala/rules/MagicWandSupporter.scala | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index 6974baea1..d876d1db7 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -263,16 +263,15 @@ class TermToSMTLib2Converter case Domain(id, fvf) => parens(text("$FVF.domain_") <> id <+> render(fvf)) - case Lookup(field, fvf, at) => - // fvf.sort match { - // case _: sorts.PartialFieldValueFunction => + case Lookup(field, fvf, at) => //fvf.sort match { +// case _: sorts.PartialFieldValueFunction => parens(text("$FVF.lookup_") <> field <+> render(fvf) <+> render(at)) - // case _: sorts.TotalFieldValueFunction => - // render(Apply(fvf, Seq(at))) - // parens("$FVF.lookup_" <> field <+> render(fvf) <+> render(at)) - // case _ => - // sys.error(s"Unexpected sort '${fvf.sort}' of field value function '$fvf' in lookup term '$term'") - // } +// case _: sorts.TotalFieldValueFunction => +// render(Apply(fvf, Seq(at))) +// parens("$FVF.lookup_" <> field <+> render(fvf) <+> render(at)) +// case _ => +// sys.error(s"Unexpected sort '${fvf.sort}' of field value function '$fvf' in lookup term '$term'") +// } case FieldTrigger(field, fvf, at) => parens(text("$FVF.loc_") <> field <+> (fvf.sort match { case sorts.FieldValueFunction(_, _) => render(Lookup(field, fvf, at)) <+> render(at) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 22c333a8b..98ade45ff 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -301,7 +301,6 @@ object magicWandSupporter extends SymbolicExecutionRules { appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) - } else { this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly From e669d261e2519c4bc7e6ec4b253cf51ec54750e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 17 May 2024 11:02:16 +0200 Subject: [PATCH 12/43] Simplify havoc --- src/main/scala/rules/HavocSupporter.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 920d39dd5..d146c72ee 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -183,12 +183,9 @@ object havocSupporter extends SymbolicExecutionRules { val newChunks = relevantChunks.map { case ch: MagicWandChunk => - val havockedSnap = freshSnap(sorts.Snap, v) - val abstractLhs = freshSnap(sorts.Snap, v) + val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val cond = replacementCond(lhs, ch.args, condInfo) - - // Define a new `MagicWandSnapshot` that checks the havoc condition when being applied - val magicWandSnapshot = magicWandSupporter.createMagicWandSnapshot(abstractLhs, Ite(cond, havockedSnap, MWSFLookup(ch.snap.mwsf, abstractLhs)), v) + val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) ch.withSnap(magicWandSnapshot) case ch => From 336eda3422172b5c9a74852ecdd261c9575e7052 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Fri, 17 May 2024 14:04:44 +0200 Subject: [PATCH 13/43] Simplify production of a MWSF. --- src/main/scala/rules/Producer.scala | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a648d6256..a06ce4848 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -398,17 +398,7 @@ object producer extends ProductionRules { case wand: ast.MagicWand => val snapRhs = sf(sorts.MagicWandSnapFunction, v) - - // Create MWSF that takes a snapshot, which represent the values of the consumed LHS of the wand, - // and relates it to the snapshot of the RHS. We use this to preserve values of the LHS in the RHS snapshot. - v.decider.prover.comment(s"Produce MagicWandSnapFunction for wand $wand") - val abstractLhs = v.decider.fresh(sorts.Snap) - - // We assume that the MWSF that we get from `snapRhs` is the same as our newly created MWSF. - // `snapRhs` is an expression that potentially returns a nested element of a binary tree. - val magicWandSnapshot = magicWandSupporter.createMagicWandSnapshot(abstractLhs, MWSFLookup(snapRhs, abstractLhs), v) - - magicWandSupporter.createChunk(s, wand, magicWandSnapshot, pve, v)((s1, chWand, v1) => + magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snapRhs), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) From 82239ac3df2324410714d415189767a04aa0e31b Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 19 May 2024 09:28:52 +0200 Subject: [PATCH 14/43] Rename variable in Producer. --- src/main/scala/rules/Producer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index a06ce4848..f3976c827 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,8 +397,8 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snapRhs = sf(sorts.MagicWandSnapFunction, v) - magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snapRhs), pve, v)((s1, chWand, v1) => + val snap = sf(sorts.MagicWandSnapFunction, v) + magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) From 0761aa9825efe96dd818fac67f9d2063f15cfe2e Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 19 May 2024 12:35:36 +0200 Subject: [PATCH 15/43] [WIP] Use MWSF for quantified magic wands. --- .../decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 56 ++++++++++++++----- .../rules/MoreCompleteExhaleSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 6 +- .../scala/rules/QuantifiedChunkSupport.scala | 8 ++- src/main/scala/state/Chunks.scala | 2 +- src/main/scala/state/Terms.scala | 8 +-- .../MagicWandSnapFunctionsContributor.scala | 2 +- ...icateAndWandSnapFunctionsContributor.scala | 4 +- 10 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index d876d1db7..ae92bb139 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -62,7 +62,7 @@ class TermToSMTLib2Converter case sorts.FieldPermFunction() => text("$FPM") case sorts.PredicatePermFunction() => text("$PPM") - case sorts.MagicWandSnapFunction => text("$MWSF") + case sorts.MagicWandSnapFunction() => text("$MWSF") } def convert(d: Decl): String = { diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index d146c72ee..59921fbb0 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -183,7 +183,7 @@ object havocSupporter extends SymbolicExecutionRules { val newChunks = relevantChunks.map { case ch: MagicWandChunk => - val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) ch.withSnap(magicWandSnapshot) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 98ade45ff..3a430de47 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -108,7 +108,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, @@ -236,10 +236,6 @@ object magicWandSupporter extends SymbolicExecutionRules { val s = if (state.exhaleExt) state else state.copy(reserveHeaps = Heap() :: state.h :: Nil) - // v.logger.debug(s"wand = $wand") - // v.logger.debug("c.reserveHeaps:") - // s.reserveHeaps.map(v.stateFormatter.format).foreach(str => v.logger.debug(str, 2)) - val stackSize = 3 + s.reserveHeaps.tail.size // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below var recordedBranches: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[Term], Chunk)] = Nil @@ -284,7 +280,13 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val wandSnapshot = this.createMagicWandSnapshot(freshSnapRoot, snapRhs, v3) + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) + val wandSnapshot = MagicWandSnapshot(mwsf) + v.decider.assumeDefinition(Forall( + freshSnapRoot, + MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + Trigger(MWSFLookup(mwsf, freshSnapRoot)) + )) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -292,13 +294,40 @@ object magicWandSupporter extends SymbolicExecutionRules { val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { - val snapshotTerm = Combine(freshSnapRoot, snapRhs) - val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, snapshotTerm, v4) + // val snapshotTerm = Combine(freshSnapRoot, snapRhs) + val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, mwsf, v4) v4.decider.prover.comment("Definitional axioms for singleton-SM's value") - v4.decider.assumeDefinition(smValueDef) + + // v4.decider.assumeDefinition(Forall( + // freshSnapRoot, + // MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + // Trigger(MWSFLookup(mwsf, freshSnapRoot)) + // )) + // v4.decider.assumeDefinition(smValueDef) + v4.decider.assumeDefinition(Forall( + freshSnapRoot, + MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot) === snapRhs, + Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)) + )) + val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - val conservedPcs = (s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly).flatMap(_.conditionalized) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) + // val conservedPcs = (s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly).flatMap(_.conditionalized) + // appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) + + val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly + // Partition path conditions into a set which include the freshSnapRoot and those which do not + val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(freshSnapRoot)) + // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier + val pcsQuantified = Forall( + freshSnapRoot, + And(pcsWithFreshSnapRoot.map { + // Remove forall quantifiers with the same quantified variable + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body + case p => p + }), + Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)), + ) + appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) Success() }) } else { @@ -444,8 +473,9 @@ object magicWandSupporter extends SymbolicExecutionRules { case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs) // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - v2.decider.assume(snapLhs === First(snapWand)) - Second(predicateLookup) + MWSFLookup(predicateLookup, snapLhs) + case SortWrapper(predicateLookup: PredicateLookup, _) => + MWSFLookup(predicateLookup, snapLhs) case _ => snapWand } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index 8e983f573..eca8fb801 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -55,7 +55,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { resource match { case f: ast.Field => v.symbolConverter.toSort(f.typ) case _: ast.Predicate => sorts.Snap - case _: ast.MagicWand => sorts.MagicWandSnapFunction + case _: ast.MagicWand => sorts.MagicWandSnapFunction() } val `?s` = Var(Identifier("?s"), sort, false) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index f3976c827..ba233a3d9 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -367,7 +367,7 @@ object producer extends ProductionRules { val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) evals(s, bodyVars, _ => pve, v)((s1, args,v1) => { val (sm, smValueDef) = - quantifiedChunkSupporter.singletonSnapshotMap(s1, wand, args, sf(sorts.Snap, v1), v1) + quantifiedChunkSupporter.singletonSnapshotMap(s1, wand, args, sf(sorts.MagicWandSnapFunction(), v1), v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val definitionalAxiomMark = v1.decider.setPathConditionMark() v1.decider.assumeDefinition(smValueDef) @@ -397,7 +397,7 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snap = sf(sorts.MagicWandSnapFunction, v) + val snap = sf(sorts.MagicWandSnapFunction(), v) magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) @@ -477,7 +477,7 @@ object producer extends ProductionRules { val qid = MagicWandIdentifier(wand, s.program).toString evalQuantified(s, Forall, forall.variables, Seq(cond), bodyVars, optTrigger, qid, pve, v) { case (s1, qvars, Seq(tCond), Some((tArgs, tTriggers, (auxGlobals, auxNonGlobals))), v1) => - val tSnap = sf(sorts.PredicateSnapFunction(sorts.Snap, qid), v1) + val tSnap = sf(sorts.PredicateSnapFunction(sorts.MagicWandSnapFunction(), qid), v1) quantifiedChunkSupporter.produce( s1, forall, diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index ed53b6ef9..a7fe9974c 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -640,6 +640,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { def singletonSnapshotMap(s: State, resource: ast.Resource, arguments: Seq[Term], + // freshSnapRoot: Term, value: Term, v: Verifier) : (Term, Term) = { @@ -647,6 +648,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val additionalSmArgs = s.relevantQuantifiedVariables(arguments) val sm = freshSnapshotMap(s, resource, additionalSmArgs, v) val smValueDef = BuiltinEquals(ResourceLookup(resource, sm, arguments, s.program), value) + // val smValueDef = Forall( + // freshSnapRoot, + // MWSFLookup(ResourceLookup(resource, sm, arguments, s.program), freshSnapRoot) === snapRhs, + // Trigger(MWSFLookup(ResourceLookup(resource, sm, arguments, s.program), freshSnapRoot)) + // ) (sm, smValueDef) } @@ -1560,7 +1566,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Reconsider use of and general design behind s.predicateSnapMap sorts.PredicateSnapFunction(s.predicateSnapMap(predicate), predicate.name) case w: ast.MagicWand => - sorts.PredicateSnapFunction(sorts.Snap, MagicWandIdentifier(w, s.program).toString) + sorts.PredicateSnapFunction(sorts.MagicWandSnapFunction(), MagicWandIdentifier(w, s.program).toString) case _ => sys.error(s"Found yet unsupported resource $resource (${resource.getClass.getSimpleName})") } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 44b907031..00df93892 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -131,7 +131,7 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, hints: Seq[Term] = Nil) extends QuantifiedBasicChunk { - require(wsf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction] && wsf.sort.asInstanceOf[terms.sorts.PredicateSnapFunction].codomainSort == sorts.Snap, s"Quantified magic wand chunk values must be of sort MagicWandSnapFunction ($wsf), but found ${wsf.sort}") + require(wsf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction] && wsf.sort.asInstanceOf[terms.sorts.PredicateSnapFunction].codomainSort.isInstanceOf[sorts.MagicWandSnapFunction], s"Quantified magic wand chunk values must be of sort MagicWandSnapFunction ($wsf), but found ${wsf.sort}") require(perm.sort == sorts.Perm, s"Permissions $perm must be of sort Perm, but found ${perm.sort}") override val resourceID = MagicWandID diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index e08f4854b..389a3f699 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -76,7 +76,7 @@ object sorts { override lazy val toString = id.toString } - object MagicWandSnapFunction extends Sort { + case class MagicWandSnapFunction() extends Sort { val id: Identifier = Identifier("MWSF") override lazy val toString: String = id.toString } @@ -2308,9 +2308,9 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * and uses this function and the resulting snapshot to look up which right-hand side to produce. */ class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { - utils.assertSort(mwsf, "magic wand snap function", sorts.MagicWandSnapFunction) + utils.assertSort(mwsf, "magic wand snap function", "MagicWandSnapFunction", _.isInstanceOf[sorts.MagicWandSnapFunction]) - override val sort: Sort = sorts.MagicWandSnapFunction + override val sort: Sort = sorts.MagicWandSnapFunction() override lazy val toString = s"wandSnap($mwsf)" @@ -2349,7 +2349,7 @@ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFl object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { override def apply(pair: (Term, Term)): MWSFLookup = { val (mwsf, snap) = pair - utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) + utils.assertSort(mwsf, "mwsf", "MagicWandSnapFunction", _.isInstanceOf[sorts.MagicWandSnapFunction]) utils.assertSort(snap, "snap", sorts.Snap) createIfNonExistent(pair) } diff --git a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala index c2b925c6a..6ab16594f 100644 --- a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala @@ -42,7 +42,7 @@ class MagicWandSnapFunctionsContributor(preambleReader: PreambleReader[String, S // If there are not magic wands, do not add any definitions or axioms if (!program.existsDefined { case ast.MagicWand(_, _) => true }) return - collectedSorts = InsertionOrderedSet(sorts.MagicWandSnapFunction) + collectedSorts = InsertionOrderedSet(sorts.MagicWandSnapFunction()) collectedFunctionDecls = preambleReader.readPreamble(FILE_DECLARATIONS) } diff --git a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala index 3da5802d1..d7561458b 100644 --- a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala @@ -129,7 +129,7 @@ class DefaultPredicateAndWandSnapFunctionsContributor(preambleReader: PreambleRe val wandsPreamble = collectedWandIdentifiers map (wandIdentifier => { - val snapSort = sorts.Snap + val snapSort = sorts.MagicWandSnapFunction() val id = wandIdentifier.toString val substitutions = Map("$PRD$" -> id, "$S$" -> termConverter.convert(snapSort)) val declarations = preambleReader.readParametricPreamble(snapsTemplateFile, substitutions) @@ -157,7 +157,7 @@ class DefaultPredicateAndWandSnapFunctionsContributor(preambleReader: PreambleRe val wandsPreamble = collectedWandIdentifiers map (wandIdentifier => { - val sort = sorts.Snap // predicateSnapGenerator.getSnap(wandIdentifier)._1 + val sort = sorts.MagicWandSnapFunction() // predicateSnapGenerator.getSnap(wandIdentifier)._1 val id = wandIdentifier.toString val substitutions = Map("$PRD$" -> id, "$S$" -> termConverter.convert(sort)) val declarations = preambleReader.readParametricPreamble(templateFile, substitutions) From 5441d10257086cc9a2fa5adf46c56b4ebd28d2fe Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:17:48 +0200 Subject: [PATCH 16/43] Recursively replace all quantification in the PCS with the freshSnapRoot. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/silver b/silver index ca7983460..e040eb86d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit ca79834608bd2f4660fae0ee7d7b1a3ecabc9a32 +Subproject commit e040eb86d810921ab56ba9355eaec1a32fd70dfd diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 3a430de47..8430eab6e 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -320,11 +320,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier val pcsQuantified = Forall( freshSnapRoot, - And(pcsWithFreshSnapRoot.map { + And(pcsWithFreshSnapRoot.map(t => t.transform { // Remove forall quantifiers with the same quantified variable case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - case p => p - }), + }(_ => true))), Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)), ) appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) @@ -338,11 +337,10 @@ object magicWandSupporter extends SymbolicExecutionRules { // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier val pcsQuantified = Forall( freshSnapRoot, - And(pcsWithFreshSnapRoot.map { + And(pcsWithFreshSnapRoot.map(t => t.transform { // Remove forall quantifiers with the same quantified variable case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - case p => p - }), + }(_ => true))), Trigger(MWSFLookup(wandSnapshot.mwsf, freshSnapRoot)), ) From a5c55c07b1f9935f36fd1ebb0751bcd25db65d93 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:00:49 +0200 Subject: [PATCH 17/43] Try to use implications. --- src/main/scala/rules/MagicWandSupporter.scala | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 8430eab6e..c18c4dca8 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -281,12 +281,6 @@ object magicWandSupporter extends SymbolicExecutionRules { v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) - val wandSnapshot = MagicWandSnapshot(mwsf) - v.decider.assumeDefinition(Forall( - freshSnapRoot, - MWSFLookup(mwsf, freshSnapRoot) === snapRhs, - Trigger(MWSFLookup(mwsf, freshSnapRoot)) - )) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -320,16 +314,20 @@ object magicWandSupporter extends SymbolicExecutionRules { // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier val pcsQuantified = Forall( freshSnapRoot, - And(pcsWithFreshSnapRoot.map(t => t.transform { - // Remove forall quantifiers with the same quantified variable - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - }(_ => true))), + Implies( + And(pcsWithFreshSnapRoot.map(t => t.transform { + // Remove forall quantifiers with the same quantified variable + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body + }(_ => true))), + MWSFLookup(mwsf, freshSnapRoot) === snapRhs + ), Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)), ) appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) Success() }) } else { + val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly // Partition path conditions into a set which include the freshSnapRoot and those which do not @@ -337,12 +335,20 @@ object magicWandSupporter extends SymbolicExecutionRules { // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier val pcsQuantified = Forall( freshSnapRoot, - And(pcsWithFreshSnapRoot.map(t => t.transform { - // Remove forall quantifiers with the same quantified variable - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - }(_ => true))), + Implies( + And(pcsWithFreshSnapRoot.map(t => t.transform { + // Remove forall quantifiers with the same quantified variable + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body + }(_ => true))), + MWSFLookup(mwsf, freshSnapRoot) === snapRhs + ), Trigger(MWSFLookup(wandSnapshot.mwsf, freshSnapRoot)), ) + v4.decider.assumeDefinition(Forall( + freshSnapRoot, + MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + Trigger(MWSFLookup(mwsf, freshSnapRoot)) + )) appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) Success() From 00f5f111c766b63ecd153c2065fe9f76f302b76e Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:21:11 +0200 Subject: [PATCH 18/43] Record which snapshot maps were created during packaging. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 147 +++++++++++------- 2 files changed, 91 insertions(+), 58 deletions(-) diff --git a/silver b/silver index e040eb86d..129284b4e 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit e040eb86d810921ab56ba9355eaec1a32fd70dfd +Subproject commit 129284b4e8df75ac525c27abde06c7294421febe diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index c18c4dca8..59c12f942 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -272,9 +272,85 @@ object magicWandSupporter extends SymbolicExecutionRules { recordedBranches :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) } + /** + * Partition path conditions into a set which include the freshSnapRoot and those which do not + * and combine them with the path conditions which are not conditionalized. + * + * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. + * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. + * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. + * @param snapRhs Snapshot of the wand's RHS. + * @param functionsBeforePackaging Set of functions that were defined during the packaging of the wand. + * @param v1 Verifier instance. + * @return Vector of conserved path conditions. + */ + def partitionAndCombineConservedPathConditions(conservedPcs: Vector[RecordedPathConditions], + freshSnapRoot: Var, + mwsf: Var, + snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], + v1: Verifier): Vector[Term] = { + // Find all snapshot maps that were defined during the packaging of the wand + val freshSnapshotMaps = (v1.decider.freshFunctions -- functionsBeforePackaging) + .filter(_.func.resultSort.isInstanceOf[sorts.FieldValueFunction]) + .map(f => App(f.func, Seq.empty)) + .toSeq + var quantifiedVars = freshSnapRoot +: freshSnapshotMaps + + // Partition path conditions into those which include the freshSnapRoot and those which do not + val (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = conservedPcs + .flatMap(_.conditionalized) + .partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) + + // Remove forall quantifiers with the same quantified variable + var transformedPcs = pcsWithQuantifiedVars.map(_.transform { + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if quantifiedVars.contains(v) => body + }(_ => true)) + + // If there are more than one new snapshot maps, which are assumed to be the same, combine them into a single term + // if (freshSnapshotMaps.length == 2) { + // if (pcsWithQuantifiedVars.exists({ + // _.existsDefined(t => t.isInstanceOf[BuiltinEquals] && + // t.asInstanceOf[BuiltinEquals].p0 == freshSnapshotMaps.head && + // t.asInstanceOf[BuiltinEquals].p1 == freshSnapshotMaps.last + // ) + // })) { + // quantifiedVars = Seq(freshSnapRoot, freshSnapshotMaps.last) + // transformedPcs = transformedPcs.map(pcs => { + // pcs.transform({ + // // case v: Var if (v.equals(freshSnapshotMaps.head)) => freshSnapshotMaps.last + // case app: App if app.applicable.id == freshSnapshotMaps.head.applicable.id => { + // freshSnapshotMaps.last + // } + // case t => t + // })(_ => true) + // }) + // } + // } else if (freshSnapshotMaps.length > 2) { + // throw new Exception("Please let me know. There are more than two snapshot maps!") + // } + + val pcsQuantified = Forall( + quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)), + Implies( + And(transformedPcs), + MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + ), + Trigger(MWSFLookup(mwsf, freshSnapRoot)) + ) + + v1.decider.assumeDefinition(Forall( + freshSnapRoot, + MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + Trigger(MWSFLookup(mwsf, freshSnapRoot)) + )) + pcsQuantified +: pcsWithoutQuantifiedVars + } + def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], v3: Verifier) : VerificationResult = { val preMark = v3.decider.setPathConditionMark() @@ -290,67 +366,22 @@ object magicWandSupporter extends SymbolicExecutionRules { evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { // val snapshotTerm = Combine(freshSnapRoot, snapRhs) val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, mwsf, v4) - v4.decider.prover.comment("Definitional axioms for singleton-SM's value") - - // v4.decider.assumeDefinition(Forall( - // freshSnapRoot, - // MWSFLookup(mwsf, freshSnapRoot) === snapRhs, - // Trigger(MWSFLookup(mwsf, freshSnapRoot)) - // )) - // v4.decider.assumeDefinition(smValueDef) - v4.decider.assumeDefinition(Forall( - freshSnapRoot, - MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot) === snapRhs, - Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)) - )) - + // v4.decider.prover.comment("Definitional axioms for singleton-SM's value") val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - // val conservedPcs = (s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly).flatMap(_.conditionalized) - // appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) - - val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly - // Partition path conditions into a set which include the freshSnapRoot and those which do not - val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(freshSnapRoot)) - // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier - val pcsQuantified = Forall( - freshSnapRoot, - Implies( - And(pcsWithFreshSnapRoot.map(t => t.transform { - // Remove forall quantifiers with the same quantified variable - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - }(_ => true))), - MWSFLookup(mwsf, freshSnapRoot) === snapRhs - ), - Trigger(MWSFLookup(ResourceLookup(wand, sm, args, s5.program), freshSnapRoot)), - ) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) + val conservedPcs = partitionAndCombineConservedPathConditions( + s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, + freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) + appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { - val conservedPcs = s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly - // Partition path conditions into a set which include the freshSnapRoot and those which do not - val (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conservedPcs.flatMap(_.conditionalized).partition(pcs => pcs.contains(freshSnapRoot)) - // For all path conditions which include the freshSnapRoot, add those as part of the definition of the MWSF in the same forall quantifier - val pcsQuantified = Forall( - freshSnapRoot, - Implies( - And(pcsWithFreshSnapRoot.map(t => t.transform { - // Remove forall quantifiers with the same quantified variable - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v == freshSnapRoot => body - }(_ => true))), - MWSFLookup(mwsf, freshSnapRoot) === snapRhs - ), - Trigger(MWSFLookup(wandSnapshot.mwsf, freshSnapRoot)), - ) - v4.decider.assumeDefinition(Forall( - freshSnapRoot, - MWSFLookup(mwsf, freshSnapRoot) === snapRhs, - Trigger(MWSFLookup(mwsf, freshSnapRoot)) - )) - - appendToResults(s5, ch, v4.decider.pcs.after(preMark), pcsQuantified +: pcsWithoutFreshSnapRoot, v4) + val conservedPcs = partitionAndCombineConservedPathConditions( + s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, + freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) + + appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) } @@ -393,6 +424,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * or `unfoldingPredicate` below. */ assert(stackSize == s2.reserveHeaps.length) + val functionsBeforePackaging = v2.decider.freshFunctions // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. @@ -404,7 +436,7 @@ object magicWandSupporter extends SymbolicExecutionRules { wand.right, pve, proofScriptVerifier )((s3, snapRhs, v3) => { - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, v3) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, functionsBeforePackaging, v3) }) }) }) @@ -415,7 +447,8 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) + val functionsBeforePackaging = v.decider.freshFunctions + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), functionsBeforePackaging, v) } recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { From 142cf3237cba14e544dcd598cec451dc15983ed1 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:27:38 +0200 Subject: [PATCH 19/43] Work on QPFields test cases. --- src/main/scala/rules/MagicWandSupporter.scala | 126 ++++++++++-------- 1 file changed, 74 insertions(+), 52 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 59c12f942..437b6ee8b 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -16,7 +16,7 @@ import viper.silicon.decider.RecordedPathConditions import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ -import viper.silicon.state.terms.{MagicWandSnapshot, _} +import viper.silicon.state.terms._ import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier @@ -273,8 +273,9 @@ object magicWandSupporter extends SymbolicExecutionRules { } /** - * Partition path conditions into a set which include the freshSnapRoot and those which do not - * and combine them with the path conditions which are not conditionalized. + * Partition path conditions into a set which include the freshSnapRoot and those which do not. + * Include the path conditions with the freshSnapRoot in the MWSF definition. + * It also takes care of special cases that include field value functions. * * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. @@ -284,67 +285,88 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param v1 Verifier instance. * @return Vector of conserved path conditions. */ - def partitionAndCombineConservedPathConditions(conservedPcs: Vector[RecordedPathConditions], - freshSnapRoot: Var, - mwsf: Var, - snapRhs: Term, - functionsBeforePackaging: Set[FunctionDecl], - v1: Verifier): Vector[Term] = { - // Find all snapshot maps that were defined during the packaging of the wand - val freshSnapshotMaps = (v1.decider.freshFunctions -- functionsBeforePackaging) - .filter(_.func.resultSort.isInstanceOf[sorts.FieldValueFunction]) - .map(f => App(f.func, Seq.empty)) - .toSeq - var quantifiedVars = freshSnapRoot +: freshSnapshotMaps - - // Partition path conditions into those which include the freshSnapRoot and those which do not - val (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = conservedPcs - .flatMap(_.conditionalized) - .partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) + def summarizeDefinitions(conservedPcs: Vector[RecordedPathConditions], + freshSnapRoot: Var, + mwsf: Var, + snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], + v1: Verifier): Vector[Term] = { + // Map all path conditions to their conditionalized form and flatten the result + val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { + case And(terms) => terms + case term => Vector(term) + } + + // Rewrite path conditions which include the snapRhs when it is a field value function (FVF) + val (unchangedPcs, updatedPcs) = snapRhs match { + // Rewrite based on test11 in QPFields.vpr + case SortWrapper(app: App, _) if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => + conditionalizedPcs.partitionMap { + case Quantification(Forall, Seq(r), Implies(cond, BuiltinEquals(Lookup(field, fvf, at), rhs)), _, _, _, _) + if r.sort == sorts.Ref && fvf == app && r == at => + + val newLookup = Lookup(field, SortWrapper(MWSFLookup(mwsf, freshSnapRoot), fvf.sort), at) + val quantification = Forall( + Seq(r, freshSnapRoot), + Implies(cond, BuiltinEquals(newLookup, rhs)), + Trigger(newLookup) + ) + v1.decider.assumeDefinition(quantification) + Right(quantification) + case pcs => Left(pcs) + } + + // // Rewrite for test9 in QPFields.vpr -> leads to unsoundness in snap_functions/test13.vpr + // case SortWrapper(Lookup(_, app: App, _), _) + // if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => + // + // conditionalizedPcs.partitionMap { + // case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) + // if fvf == app => + // + // val newLhs = SortWrapper(MWSFLookup(mwsf, freshSnapRoot), rhs.sort) + // val quantification = Forall( + // freshSnapRoot, + // Implies(cond, BuiltinEquals(newLhs, rhs)), + // Seq(Trigger(newLhs), Trigger(rhs)) + // ) + // v1.decider.assumeDefinition(quantification) + // Right(quantification) + // case pcs => Left(pcs) + // } + + case _ => (conservedPcs.flatMap(_.conditionalized), Vector.empty) + } // Remove forall quantifiers with the same quantified variable - var transformedPcs = pcsWithQuantifiedVars.map(_.transform { - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if quantifiedVars.contains(v) => body - }(_ => true)) - - // If there are more than one new snapshot maps, which are assumed to be the same, combine them into a single term - // if (freshSnapshotMaps.length == 2) { - // if (pcsWithQuantifiedVars.exists({ - // _.existsDefined(t => t.isInstanceOf[BuiltinEquals] && - // t.asInstanceOf[BuiltinEquals].p0 == freshSnapshotMaps.head && - // t.asInstanceOf[BuiltinEquals].p1 == freshSnapshotMaps.last - // ) - // })) { - // quantifiedVars = Seq(freshSnapRoot, freshSnapshotMaps.last) - // transformedPcs = transformedPcs.map(pcs => { - // pcs.transform({ - // // case v: Var if (v.equals(freshSnapshotMaps.head)) => freshSnapshotMaps.last - // case app: App if app.applicable.id == freshSnapshotMaps.head.applicable.id => { - // freshSnapshotMaps.last - // } - // case t => t - // })(_ => true) - // }) - // } - // } else if (freshSnapshotMaps.length > 2) { - // throw new Exception("Please let me know. There are more than two snapshot maps!") - // } + val pcsWithFreshSnapRoot = unchangedPcs + .filter(pcs => pcs.contains(freshSnapRoot)) + .map(_.transform { + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body + }(_ => true)) + + // Collect all path conditions which do not include the freshSnapRoot + val pcsWithoutFreshSnapRoot = conditionalizedPcs.filter(pcs => !pcs.contains(freshSnapRoot)) + // Combine all path conditions which include the freshSnapRoot val pcsQuantified = Forall( - quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)), + freshSnapRoot, Implies( - And(transformedPcs), + And(pcsWithFreshSnapRoot), MWSFLookup(mwsf, freshSnapRoot) === snapRhs, ), Trigger(MWSFLookup(mwsf, freshSnapRoot)) ) + // Add this definition to the path conditions for outer package operations v1.decider.assumeDefinition(Forall( freshSnapRoot, MWSFLookup(mwsf, freshSnapRoot) === snapRhs, Trigger(MWSFLookup(mwsf, freshSnapRoot)) )) - pcsQuantified +: pcsWithoutQuantifiedVars + + // Return the summarized path conditions + pcsWithoutFreshSnapRoot ++ updatedPcs :+ pcsQuantified } def createWandChunkAndRecordResults(s4: State, @@ -368,7 +390,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, mwsf, v4) // v4.decider.prover.comment("Definitional axioms for singleton-SM's value") val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - val conservedPcs = partitionAndCombineConservedPathConditions( + + val conservedPcs = summarizeDefinitions( s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) @@ -377,10 +400,9 @@ object magicWandSupporter extends SymbolicExecutionRules { } else { val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { - val conservedPcs = partitionAndCombineConservedPathConditions( + val conservedPcs = summarizeDefinitions( s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) From 6131c0f1acaa40624631864b622d44566eedc6ab Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 11 Jun 2024 09:02:04 +0200 Subject: [PATCH 20/43] Optimize for test9 in QPFields.vpr --- src/main/scala/rules/MagicWandSupporter.scala | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 437b6ee8b..030528162 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -297,11 +297,20 @@ object magicWandSupporter extends SymbolicExecutionRules { case term => Vector(term) } + // Partition path conditions into a set which include the freshSnapRoot and those which do not + var (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conditionalizedPcs.partition(pcs => pcs.contains(freshSnapRoot)) + + // Remove forall quantifiers with the same quantified variable + pcsWithFreshSnapRoot = pcsWithFreshSnapRoot + .map(_.transform { + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body + }(_ => true)) + // Rewrite path conditions which include the snapRhs when it is a field value function (FVF) - val (unchangedPcs, updatedPcs) = snapRhs match { + val updatedPcs = snapRhs match { // Rewrite based on test11 in QPFields.vpr case SortWrapper(app: App, _) if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => - conditionalizedPcs.partitionMap { + conditionalizedPcs.flatMap { case Quantification(Forall, Seq(r), Implies(cond, BuiltinEquals(Lookup(field, fvf, at), rhs)), _, _, _, _) if r.sort == sorts.Ref && fvf == app && r == at => @@ -312,41 +321,29 @@ object magicWandSupporter extends SymbolicExecutionRules { Trigger(newLookup) ) v1.decider.assumeDefinition(quantification) - Right(quantification) - case pcs => Left(pcs) + Vector(quantification) + case _ => Vector.empty } - // // Rewrite for test9 in QPFields.vpr -> leads to unsoundness in snap_functions/test13.vpr - // case SortWrapper(Lookup(_, app: App, _), _) - // if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => - // - // conditionalizedPcs.partitionMap { - // case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) - // if fvf == app => - // - // val newLhs = SortWrapper(MWSFLookup(mwsf, freshSnapRoot), rhs.sort) - // val quantification = Forall( - // freshSnapRoot, - // Implies(cond, BuiltinEquals(newLhs, rhs)), - // Seq(Trigger(newLhs), Trigger(rhs)) - // ) - // v1.decider.assumeDefinition(quantification) - // Right(quantification) - // case pcs => Left(pcs) - // } - - case _ => (conservedPcs.flatMap(_.conditionalized), Vector.empty) - } + // Rewrite for test9 in QPFields.vpr + case SortWrapper(Lookup(_, app: App, _), to) + if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => - // Remove forall quantifiers with the same quantified variable - val pcsWithFreshSnapRoot = unchangedPcs - .filter(pcs => pcs.contains(freshSnapRoot)) - .map(_.transform { - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body - }(_ => true)) + conditionalizedPcs.flatMap { + case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) if fvf == app => + val newLhs = MWSFLookup(mwsf, freshSnapRoot) + val quantification = Forall( + freshSnapRoot, + Implies(cond, BuiltinEquals(newLhs, SortWrapper(rhs, to))), + Seq(Trigger(newLhs), Trigger(rhs)) + ) + v1.decider.assumeDefinition(quantification) + Vector(quantification) + case _ => Vector.empty + } - // Collect all path conditions which do not include the freshSnapRoot - val pcsWithoutFreshSnapRoot = conditionalizedPcs.filter(pcs => !pcs.contains(freshSnapRoot)) + case _ => Vector.empty + } // Combine all path conditions which include the freshSnapRoot val pcsQuantified = Forall( From e00837ec7beb5e12876f32d48d438e80b6080c49 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:47:31 +0200 Subject: [PATCH 21/43] Let PredicateSnapFunctions be of sort Snap and use SortWrappers to convert them to MWSF. --- src/main/scala/rules/MagicWandSupporter.scala | 7 +++---- src/main/scala/rules/Producer.scala | 2 +- src/main/scala/rules/QuantifiedChunkSupport.scala | 2 +- src/main/scala/state/Chunks.scala | 2 +- .../qps/PredicateAndWandSnapFunctionsContributor.scala | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 030528162..99906ae2d 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -335,7 +335,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val quantification = Forall( freshSnapRoot, Implies(cond, BuiltinEquals(newLhs, SortWrapper(rhs, to))), - Seq(Trigger(newLhs), Trigger(rhs)) + Trigger(newLhs) ) v1.decider.assumeDefinition(quantification) Vector(quantification) @@ -527,11 +527,10 @@ object magicWandSupporter extends SymbolicExecutionRules { val magicWandSnapshotLookup = snapWand match { case snapshot: MagicWandSnapshot => snapshot.applyToMWSF(snapLhs) case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs) - // Fallback solution for quantified magic wands case predicateLookup: PredicateLookup => - MWSFLookup(predicateLookup, snapLhs) + MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction()), snapLhs) case SortWrapper(predicateLookup: PredicateLookup, _) => - MWSFLookup(predicateLookup, snapLhs) + MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction()), snapLhs) case _ => snapWand } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index ba233a3d9..1a1aa0bb3 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -477,7 +477,7 @@ object producer extends ProductionRules { val qid = MagicWandIdentifier(wand, s.program).toString evalQuantified(s, Forall, forall.variables, Seq(cond), bodyVars, optTrigger, qid, pve, v) { case (s1, qvars, Seq(tCond), Some((tArgs, tTriggers, (auxGlobals, auxNonGlobals))), v1) => - val tSnap = sf(sorts.PredicateSnapFunction(sorts.MagicWandSnapFunction(), qid), v1) + val tSnap = sf(sorts.PredicateSnapFunction(sorts.Snap, qid), v1) quantifiedChunkSupporter.produce( s1, forall, diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index a7fe9974c..8fc0e9524 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -1566,7 +1566,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { // TODO: Reconsider use of and general design behind s.predicateSnapMap sorts.PredicateSnapFunction(s.predicateSnapMap(predicate), predicate.name) case w: ast.MagicWand => - sorts.PredicateSnapFunction(sorts.MagicWandSnapFunction(), MagicWandIdentifier(w, s.program).toString) + sorts.PredicateSnapFunction(sorts.Snap, MagicWandIdentifier(w, s.program).toString) case _ => sys.error(s"Found yet unsupported resource $resource (${resource.getClass.getSimpleName})") } diff --git a/src/main/scala/state/Chunks.scala b/src/main/scala/state/Chunks.scala index 00df93892..6afb3414a 100644 --- a/src/main/scala/state/Chunks.scala +++ b/src/main/scala/state/Chunks.scala @@ -131,7 +131,7 @@ case class QuantifiedMagicWandChunk(id: MagicWandIdentifier, hints: Seq[Term] = Nil) extends QuantifiedBasicChunk { - require(wsf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction] && wsf.sort.asInstanceOf[terms.sorts.PredicateSnapFunction].codomainSort.isInstanceOf[sorts.MagicWandSnapFunction], s"Quantified magic wand chunk values must be of sort MagicWandSnapFunction ($wsf), but found ${wsf.sort}") + require(wsf.sort.isInstanceOf[terms.sorts.PredicateSnapFunction] && wsf.sort.asInstanceOf[terms.sorts.PredicateSnapFunction].codomainSort == sorts.Snap, s"Quantified magic wand chunk values must be of sort Snap ($wsf), but found ${wsf.sort}") require(perm.sort == sorts.Perm, s"Permissions $perm must be of sort Perm, but found ${perm.sort}") override val resourceID = MagicWandID diff --git a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala index d7561458b..3da5802d1 100644 --- a/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/qps/PredicateAndWandSnapFunctionsContributor.scala @@ -129,7 +129,7 @@ class DefaultPredicateAndWandSnapFunctionsContributor(preambleReader: PreambleRe val wandsPreamble = collectedWandIdentifiers map (wandIdentifier => { - val snapSort = sorts.MagicWandSnapFunction() + val snapSort = sorts.Snap val id = wandIdentifier.toString val substitutions = Map("$PRD$" -> id, "$S$" -> termConverter.convert(snapSort)) val declarations = preambleReader.readParametricPreamble(snapsTemplateFile, substitutions) @@ -157,7 +157,7 @@ class DefaultPredicateAndWandSnapFunctionsContributor(preambleReader: PreambleRe val wandsPreamble = collectedWandIdentifiers map (wandIdentifier => { - val sort = sorts.MagicWandSnapFunction() // predicateSnapGenerator.getSnap(wandIdentifier)._1 + val sort = sorts.Snap // predicateSnapGenerator.getSnap(wandIdentifier)._1 val id = wandIdentifier.toString val substitutions = Map("$PRD$" -> id, "$S$" -> termConverter.convert(sort)) val declarations = preambleReader.readParametricPreamble(templateFile, substitutions) From 6dc023f2e48bb4eb39f6926d1553cf8d06fe4511 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:48:47 +0200 Subject: [PATCH 22/43] Remove commented code. --- silver | 2 +- src/main/scala/rules/QuantifiedChunkSupport.scala | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/silver b/silver index 129284b4e..3d6fc1abb 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 129284b4e8df75ac525c27abde06c7294421febe +Subproject commit 3d6fc1abb2a3b188ea2caa932504d9665c78692b diff --git a/src/main/scala/rules/QuantifiedChunkSupport.scala b/src/main/scala/rules/QuantifiedChunkSupport.scala index 8fc0e9524..ed53b6ef9 100644 --- a/src/main/scala/rules/QuantifiedChunkSupport.scala +++ b/src/main/scala/rules/QuantifiedChunkSupport.scala @@ -640,7 +640,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { def singletonSnapshotMap(s: State, resource: ast.Resource, arguments: Seq[Term], - // freshSnapRoot: Term, value: Term, v: Verifier) : (Term, Term) = { @@ -648,11 +647,6 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport { val additionalSmArgs = s.relevantQuantifiedVariables(arguments) val sm = freshSnapshotMap(s, resource, additionalSmArgs, v) val smValueDef = BuiltinEquals(ResourceLookup(resource, sm, arguments, s.program), value) - // val smValueDef = Forall( - // freshSnapRoot, - // MWSFLookup(ResourceLookup(resource, sm, arguments, s.program), freshSnapRoot) === snapRhs, - // Trigger(MWSFLookup(ResourceLookup(resource, sm, arguments, s.program), freshSnapRoot)) - // ) (sm, smValueDef) } From 57f21a5682faf94b6402e3156b7a57324cc837ee Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:40:01 +0200 Subject: [PATCH 23/43] Make use of smValueDef. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 6 +++--- src/main/scala/rules/Producer.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/silver b/silver index 3d6fc1abb..9dba1b40e 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 3d6fc1abb2a3b188ea2caa932504d9665c78692b +Subproject commit 9dba1b40e7fa6d0f2fd30bb7f3b812048aab8f69 diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 99906ae2d..cb19e3355 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -383,9 +383,9 @@ object magicWandSupporter extends SymbolicExecutionRules { val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { - // val snapshotTerm = Combine(freshSnapRoot, snapRhs) - val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, mwsf, v4) - // v4.decider.prover.comment("Definitional axioms for singleton-SM's value") + val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, SortWrapper(mwsf, sorts.Snap), v4) + v4.decider.prover.comment("Definitional axioms for singleton-SM's value") + v4.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) val conservedPcs = summarizeDefinitions( diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 1a1aa0bb3..ca52d865b 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -367,7 +367,7 @@ object producer extends ProductionRules { val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) evals(s, bodyVars, _ => pve, v)((s1, args,v1) => { val (sm, smValueDef) = - quantifiedChunkSupporter.singletonSnapshotMap(s1, wand, args, sf(sorts.MagicWandSnapFunction(), v1), v1) + quantifiedChunkSupporter.singletonSnapshotMap(s1, wand, args, sf(sorts.Snap, v1), v1) v1.decider.prover.comment("Definitional axioms for singleton-SM's value") val definitionalAxiomMark = v1.decider.setPathConditionMark() v1.decider.assumeDefinition(smValueDef) From f339f59c781a8b6b331a3372a3feeab055996cb6 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 12 Jun 2024 08:15:59 +0200 Subject: [PATCH 24/43] Cleanup MagicWandSupporter. --- src/main/scala/rules/MagicWandSupporter.scala | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index cb19e3355..3f05ee8f8 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -281,7 +281,6 @@ object magicWandSupporter extends SymbolicExecutionRules { * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. * @param snapRhs Snapshot of the wand's RHS. - * @param functionsBeforePackaging Set of functions that were defined during the packaging of the wand. * @param v1 Verifier instance. * @return Vector of conserved path conditions. */ @@ -289,7 +288,6 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, mwsf: Var, snapRhs: Term, - functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { // Map all path conditions to their conditionalized form and flatten the result val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { @@ -330,7 +328,9 @@ object magicWandSupporter extends SymbolicExecutionRules { if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => conditionalizedPcs.flatMap { - case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) if fvf == app => + case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) + if fvf == app && rhs.contains(freshSnapRoot) => + val newLhs = MWSFLookup(mwsf, freshSnapRoot) val quantification = Forall( freshSnapRoot, @@ -369,7 +369,6 @@ object magicWandSupporter extends SymbolicExecutionRules { def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, snapRhs: Term, - functionsBeforePackaging: Set[FunctionDecl], v3: Verifier) : VerificationResult = { val preMark = v3.decider.setPathConditionMark() @@ -390,7 +389,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val conservedPcs = summarizeDefinitions( s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, - freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) + freshSnapRoot, mwsf, snapRhs, v4) appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) @@ -399,7 +398,7 @@ object magicWandSupporter extends SymbolicExecutionRules { this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { val conservedPcs = summarizeDefinitions( s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, - freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v4) + freshSnapRoot, mwsf, snapRhs, v4) appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) Success() }) @@ -443,7 +442,6 @@ object magicWandSupporter extends SymbolicExecutionRules { * or `unfoldingPredicate` below. */ assert(stackSize == s2.reserveHeaps.length) - val functionsBeforePackaging = v2.decider.freshFunctions // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. @@ -455,7 +453,7 @@ object magicWandSupporter extends SymbolicExecutionRules { wand.right, pve, proofScriptVerifier )((s3, snapRhs, v3) => { - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, functionsBeforePackaging, v3) + createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, v3) }) }) }) @@ -466,8 +464,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - val functionsBeforePackaging = v.decider.freshFunctions - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), functionsBeforePackaging, v) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) } recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { From 42bf90b66bd6bfaa46dc7dde30323ec8c5da2f6b Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 12 Jun 2024 09:56:35 +0200 Subject: [PATCH 25/43] Generalize substitution for PredicateSnapFunctions. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 99 +++++++++++++------ 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/silver b/silver index 9dba1b40e..a7d509fd0 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 9dba1b40e7fa6d0f2fd30bb7f3b812048aab8f69 +Subproject commit a7d509fd05eed59d195c7478d6bd0ac1cb1474c6 diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 3f05ee8f8..99079ae1c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -304,41 +304,80 @@ object magicWandSupporter extends SymbolicExecutionRules { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body }(_ => true)) - // Rewrite path conditions which include the snapRhs when it is a field value function (FVF) + val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) + // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFLookup definition val updatedPcs = snapRhs match { // Rewrite based on test11 in QPFields.vpr - case SortWrapper(app: App, _) if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => + case SortWrapper(app: App, _) if + app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || + app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => + + def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + val newTerms = terms.filter(_ match { + case BuiltinEquals(Lookup(_, fvf, at), _) => r.sort == sorts.Ref && fvf == app && r == at + case BuiltinEquals(PredicateLookup(_, psf, args), _) => r.sort == sorts.Snap && psf == app && args == List(r) + case _ => false + }).map { + case BuiltinEquals(lookup, rhs) => + val newLookup = lookup match { + case Lookup(field, fvf, at) => Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) + case PredicateLookup(predname, psf, args) => PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) + } + BuiltinEquals(newLookup, rhs) + } + if (newTerms.isEmpty) return Vector.empty + val quantification = Forall( + Seq(r, freshSnapRoot), + Implies(cond, And(newTerms)), + newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq + ) + v1.decider.assumeDefinition(quantification) + Vector(quantification) + } + conditionalizedPcs.flatMap { - case Quantification(Forall, Seq(r), Implies(cond, BuiltinEquals(Lookup(field, fvf, at), rhs)), _, _, _, _) - if r.sort == sorts.Ref && fvf == app && r == at => - - val newLookup = Lookup(field, SortWrapper(MWSFLookup(mwsf, freshSnapRoot), fvf.sort), at) - val quantification = Forall( - Seq(r, freshSnapRoot), - Implies(cond, BuiltinEquals(newLookup, rhs)), - Trigger(newLookup) - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) + case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) + case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) case _ => Vector.empty } // Rewrite for test9 in QPFields.vpr - case SortWrapper(Lookup(_, app: App, _), to) - if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] => + case SortWrapper(lookup, to) if + (lookup.isInstanceOf[Lookup] && + lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && + lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || + (lookup.isInstanceOf[PredicateLookup] && + lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && + lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => + + val app = lookup match { + case Lookup(_, fvf, _) => fvf + case PredicateLookup(_, psf, _) => psf + } + + def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + val newLhs = mwsfLookup + val newTerms = terms.filter(_ match { + case BuiltinEquals(Lookup(_, fvf, _), rhs) => fvf == app && rhs.contains(freshSnapRoot) + case BuiltinEquals(PredicateLookup(_, psf, _), rhs) => psf == app && rhs.contains(freshSnapRoot) + case _ => false + }).map(t => { + val rhs = t match { case BuiltinEquals(_, rhs) => rhs } + BuiltinEquals(newLhs, SortWrapper(rhs, to)) + }) + if (newTerms.isEmpty) return Vector.empty + val quantification = Forall( + Seq(freshSnapRoot), + Implies(cond, And(newTerms)), + Trigger(newLhs) + ) + v1.decider.assumeDefinition(quantification) + Vector(quantification) + } conditionalizedPcs.flatMap { - case Implies(cond, BuiltinEquals(Lookup(_, fvf, _), rhs)) - if fvf == app && rhs.contains(freshSnapRoot) => - - val newLhs = MWSFLookup(mwsf, freshSnapRoot) - val quantification = Forall( - freshSnapRoot, - Implies(cond, BuiltinEquals(newLhs, SortWrapper(rhs, to))), - Trigger(newLhs) - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) + case Implies(cond, And(terms)) => rewriteTerms(cond, terms) + case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) case _ => Vector.empty } @@ -350,16 +389,16 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot, Implies( And(pcsWithFreshSnapRoot), - MWSFLookup(mwsf, freshSnapRoot) === snapRhs, + BuiltinEquals(mwsfLookup, snapRhs), ), - Trigger(MWSFLookup(mwsf, freshSnapRoot)) + Trigger(mwsfLookup) ) // Add this definition to the path conditions for outer package operations v1.decider.assumeDefinition(Forall( freshSnapRoot, - MWSFLookup(mwsf, freshSnapRoot) === snapRhs, - Trigger(MWSFLookup(mwsf, freshSnapRoot)) + BuiltinEquals(mwsfLookup, snapRhs), + Trigger(mwsfLookup) )) // Return the summarized path conditions From 5b52c4a1b4d102806845b75840ae1216dd081f00 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:53:48 +0200 Subject: [PATCH 26/43] Add the full MWSF definition to the verifier's path conditions. --- silver | 2 +- src/main/scala/rules/Evaluator.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 10 ++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/silver b/silver index a7d509fd0..710d56ef7 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit a7d509fd05eed59d195c7478d6bd0ac1cb1474c6 +Subproject commit 710d56ef7f63e38d120bb17c4ee538cf50a9fdd4 diff --git a/src/main/scala/rules/Evaluator.scala b/src/main/scala/rules/Evaluator.scala index 0b999061a..059adb067 100644 --- a/src/main/scala/rules/Evaluator.scala +++ b/src/main/scala/rules/Evaluator.scala @@ -715,7 +715,7 @@ object evaluator extends EvaluationRules { if (pos.end.isEmpty) { fallbackName } else { - val file = pos.file.toString() + val file = pos.file.getFileName.toString val end = pos.end.get s"$file@${pos.start.line}@${pos.start.column}@${end.line}@${end.column}" } diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 99079ae1c..909659672 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -384,7 +384,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case _ => Vector.empty } - // Combine all path conditions which include the freshSnapRoot + // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions val pcsQuantified = Forall( freshSnapRoot, Implies( @@ -393,13 +393,7 @@ object magicWandSupporter extends SymbolicExecutionRules { ), Trigger(mwsfLookup) ) - - // Add this definition to the path conditions for outer package operations - v1.decider.assumeDefinition(Forall( - freshSnapRoot, - BuiltinEquals(mwsfLookup, snapRhs), - Trigger(mwsfLookup) - )) + v1.decider.assumeDefinition(pcsQuantified) // Return the summarized path conditions pcsWithoutFreshSnapRoot ++ updatedPcs :+ pcsQuantified From c089ebdaa809b7b67ca6760b7f529bf4bdace336 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:21:19 +0200 Subject: [PATCH 27/43] Revert change from object to case class of MagicWandSnapFunction. --- src/main/scala/decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 8 ++++---- src/main/scala/rules/MoreCompleteExhaleSupporter.scala | 2 +- src/main/scala/rules/Producer.scala | 2 +- src/main/scala/state/Terms.scala | 8 ++++---- .../supporters/MagicWandSnapFunctionsContributor.scala | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index ae92bb139..d876d1db7 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -62,7 +62,7 @@ class TermToSMTLib2Converter case sorts.FieldPermFunction() => text("$FPM") case sorts.PredicatePermFunction() => text("$PPM") - case sorts.MagicWandSnapFunction() => text("$MWSF") + case sorts.MagicWandSnapFunction => text("$MWSF") } def convert(d: Decl): String = { diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index 59921fbb0..d146c72ee 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -183,7 +183,7 @@ object havocSupporter extends SymbolicExecutionRules { val newChunks = relevantChunks.map { case ch: MagicWandChunk => - val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) + val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val cond = replacementCond(lhs, ch.args, condInfo) val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) ch.withSnap(magicWandSnapshot) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 909a1381b..50573d0ba 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -108,7 +108,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] */ def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val magicWandSnapshot = MagicWandSnapshot(mwsf) v.decider.assumeDefinition(Forall( abstractLhs, @@ -411,7 +411,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v3.decider.setPathConditionMark() v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction()) + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -562,9 +562,9 @@ object magicWandSupporter extends SymbolicExecutionRules { case snapshot: MagicWandSnapshot => snapshot.applyToMWSF(snapLhs) case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs) case predicateLookup: PredicateLookup => - MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction()), snapLhs) + MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction), snapLhs) case SortWrapper(predicateLookup: PredicateLookup, _) => - MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction()), snapLhs) + MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction), snapLhs) case _ => snapWand } diff --git a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala index eca8fb801..8e983f573 100644 --- a/src/main/scala/rules/MoreCompleteExhaleSupporter.scala +++ b/src/main/scala/rules/MoreCompleteExhaleSupporter.scala @@ -55,7 +55,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules { resource match { case f: ast.Field => v.symbolConverter.toSort(f.typ) case _: ast.Predicate => sorts.Snap - case _: ast.MagicWand => sorts.MagicWandSnapFunction() + case _: ast.MagicWand => sorts.MagicWandSnapFunction } val `?s` = Var(Identifier("?s"), sort, false) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index ca52d865b..f3976c827 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -397,7 +397,7 @@ object producer extends ProductionRules { Q(s2, v1)}) case wand: ast.MagicWand => - val snap = sf(sorts.MagicWandSnapFunction(), v) + val snap = sf(sorts.MagicWandSnapFunction, v) magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 389a3f699..e08f4854b 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -76,7 +76,7 @@ object sorts { override lazy val toString = id.toString } - case class MagicWandSnapFunction() extends Sort { + object MagicWandSnapFunction extends Sort { val id: Identifier = Identifier("MWSF") override lazy val toString: String = id.toString } @@ -2308,9 +2308,9 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * and uses this function and the resulting snapshot to look up which right-hand side to produce. */ class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { - utils.assertSort(mwsf, "magic wand snap function", "MagicWandSnapFunction", _.isInstanceOf[sorts.MagicWandSnapFunction]) + utils.assertSort(mwsf, "magic wand snap function", sorts.MagicWandSnapFunction) - override val sort: Sort = sorts.MagicWandSnapFunction() + override val sort: Sort = sorts.MagicWandSnapFunction override lazy val toString = s"wandSnap($mwsf)" @@ -2349,7 +2349,7 @@ class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFl object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { override def apply(pair: (Term, Term)): MWSFLookup = { val (mwsf, snap) = pair - utils.assertSort(mwsf, "mwsf", "MagicWandSnapFunction", _.isInstanceOf[sorts.MagicWandSnapFunction]) + utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) utils.assertSort(snap, "snap", sorts.Snap) createIfNonExistent(pair) } diff --git a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala index 6ab16594f..c2b925c6a 100644 --- a/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala +++ b/src/main/scala/supporters/MagicWandSnapFunctionsContributor.scala @@ -42,7 +42,7 @@ class MagicWandSnapFunctionsContributor(preambleReader: PreambleReader[String, S // If there are not magic wands, do not add any definitions or axioms if (!program.existsDefined { case ast.MagicWand(_, _) => true }) return - collectedSorts = InsertionOrderedSet(sorts.MagicWandSnapFunction()) + collectedSorts = InsertionOrderedSet(sorts.MagicWandSnapFunction) collectedFunctionDecls = preambleReader.readPreamble(FILE_DECLARATIONS) } From 0a4f3b5d1063e7579a340ac38289ee6baf36a4f0 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:35:33 +0200 Subject: [PATCH 28/43] Restructure MagicWandSupporter. --- src/main/scala/rules/MagicWandSupporter.scala | 306 ++++++++---------- 1 file changed, 141 insertions(+), 165 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 50573d0ba..ce5411a91 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -94,30 +94,6 @@ object magicWandSupporter extends SymbolicExecutionRules { ) } - /** - * Create a new [[viper.silicon.state.terms.MagicWandSnapshot]] - * and add the corresponding [[viper.silicon.state.terms.sorts.MagicWandSnapFunction]] definition to the state. - * - * It defines that when we apply the MagicWandSnapFunction to a snapshot `snap` - * we will get back `rhsSnapshot` that includes the values from that snapshot `snap`. - * - * @param abstractLhs The term that represent the snapshot of the consumed left-hand side of the magic wand. - * It is used as a bound variable in a forall quantifier. - * @param rhsSnapshot The term that integrates the left-hand side in the snapshot that is produced after applying the magic wand. - * @param v Verifier instance - * @return Fresh instance of [[viper.silicon.state.terms.MagicWandSnapshot]] - */ - def createMagicWandSnapshot(abstractLhs: Var, rhsSnapshot: Term, v: Verifier): MagicWandSnapshot = { - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) - val magicWandSnapshot = MagicWandSnapshot(mwsf) - v.decider.assumeDefinition(Forall( - abstractLhs, - MWSFLookup(mwsf, abstractLhs) === rhsSnapshot, - Trigger(MWSFLookup(mwsf, abstractLhs)) - )) - magicWandSnapshot - } - /** * Evaluate all expressions inside the given magic wand instance in the current state. * @@ -206,6 +182,135 @@ object magicWandSupporter extends SymbolicExecutionRules { } } + /** + * Partition path conditions into a set which include the freshSnapRoot and those which do not. + * Include the path conditions with the freshSnapRoot in the MWSF definition. + * It also takes care of special cases that include field value functions. + * + * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. + * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. + * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. + * @param snapRhs Snapshot of the wand's RHS. + * @param v1 Verifier instance. + * @return Vector of conserved path conditions. + */ + private def summarizeDefinitions(conservedPcs: Vector[RecordedPathConditions], + freshSnapRoot: Var, + mwsf: Var, + snapRhs: Term, + v1: Verifier): Vector[Term] = { + // Map all path conditions to their conditionalized form and flatten the result + val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { + case And(terms) => terms + case term => Vector(term) + } + + // Partition path conditions into a set which include the freshSnapRoot and those which do not + var (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conditionalizedPcs.partition(pcs => pcs.contains(freshSnapRoot)) + + // Remove forall quantifiers with the same quantified variable + pcsWithFreshSnapRoot = pcsWithFreshSnapRoot + .map(_.transform { + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body + }(_ => true)) + + val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) + // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFLookup definition + val updatedPcs = snapRhs match { + // Rewrite based on test11 in QPFields.vpr + case SortWrapper(app: App, _) if + app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || + app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => + + def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + val newTerms = terms.filter(_ match { + case BuiltinEquals(Lookup(_, fvf, at), _) => r.sort == sorts.Ref && fvf == app && r == at + case BuiltinEquals(PredicateLookup(_, psf, args), _) => r.sort == sorts.Snap && psf == app && args == List(r) + case _ => false + }).map { + case BuiltinEquals(lookup, rhs) => + val newLookup = lookup match { + case Lookup(field, fvf, at) => Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) + case PredicateLookup(predname, psf, args) => PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) + } + BuiltinEquals(newLookup, rhs) + } + if (newTerms.isEmpty) return Vector.empty + val quantification = Forall( + Seq(r, freshSnapRoot), + Implies(cond, And(newTerms)), + newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq + ) + v1.decider.assumeDefinition(quantification) + Vector(quantification) + } + + conditionalizedPcs.flatMap { + case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) + case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) + case _ => Vector.empty + } + + // Rewrite for test9 in QPFields.vpr + case SortWrapper(lookup, to) if + (lookup.isInstanceOf[Lookup] && + lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && + lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || + (lookup.isInstanceOf[PredicateLookup] && + lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && + lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => + + val app = lookup match { + case Lookup(_, fvf, _) => fvf + case PredicateLookup(_, psf, _) => psf + } + + def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + val newLhs = mwsfLookup + val newTerms = terms.filter(_ match { + case BuiltinEquals(Lookup(_, fvf, _), rhs) => fvf == app && rhs.contains(freshSnapRoot) + case BuiltinEquals(PredicateLookup(_, psf, _), rhs) => psf == app && rhs.contains(freshSnapRoot) + case _ => false + }).map(t => { + val rhs = t match { + case BuiltinEquals(_, rhs) => rhs + } + BuiltinEquals(newLhs, SortWrapper(rhs, to)) + }) + if (newTerms.isEmpty) return Vector.empty + val quantification = Forall( + Seq(freshSnapRoot), + Implies(cond, And(newTerms)), + Trigger(newLhs) + ) + v1.decider.assumeDefinition(quantification) + Vector(quantification) + } + + conditionalizedPcs.flatMap { + case Implies(cond, And(terms)) => rewriteTerms(cond, terms) + case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) + case _ => Vector.empty + } + + case _ => Vector.empty + } + + // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions + val pcsQuantified = Forall( + freshSnapRoot, + Implies( + And(pcsWithFreshSnapRoot), + BuiltinEquals(mwsfLookup, snapRhs), + ), + Trigger(mwsfLookup) + ) + v1.decider.assumeDefinition(pcsQuantified) + + // Return the summarized path conditions + pcsWithoutFreshSnapRoot ++ updatedPcs :+ pcsQuantified + } + /** * Package a magic wand into a chunk. It performs the computation of the wand's footprint * and captures all values associated to these locations inside the wand's snapshot. @@ -255,15 +360,21 @@ object magicWandSupporter extends SymbolicExecutionRules { recordPcs = true, parallelizeBranches = false) - def appendToResults(s5: State, ch: Chunk, pcs: RecordedPathConditions, conservedPcs: Vector[Term], v4: Verifier): Unit = { + def appendToRecordedBranches(s5: State, + ch: Chunk, + pcs: RecordedPathConditions, + freshSnapRoot: Var, + mwsf: Var, + snapRhs: Term, + v4: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") - var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs - // Producing a wand's LHS and executing the packaging proof code can introduce definitional path conditions, e.g. // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, v4) + var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs conservedPcsStack = s5.conservedPcs.tail match { @@ -274,133 +385,7 @@ object magicWandSupporter extends SymbolicExecutionRules { val s6 = s5.copy(conservedPcs = conservedPcsStack, recordPcs = s.recordPcs) recordedBranches :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) - } - - /** - * Partition path conditions into a set which include the freshSnapRoot and those which do not. - * Include the path conditions with the freshSnapRoot in the MWSF definition. - * It also takes care of special cases that include field value functions. - * - * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. - * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. - * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. - * @param snapRhs Snapshot of the wand's RHS. - * @param v1 Verifier instance. - * @return Vector of conserved path conditions. - */ - def summarizeDefinitions(conservedPcs: Vector[RecordedPathConditions], - freshSnapRoot: Var, - mwsf: Var, - snapRhs: Term, - v1: Verifier): Vector[Term] = { - // Map all path conditions to their conditionalized form and flatten the result - val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { - case And(terms) => terms - case term => Vector(term) - } - - // Partition path conditions into a set which include the freshSnapRoot and those which do not - var (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conditionalizedPcs.partition(pcs => pcs.contains(freshSnapRoot)) - - // Remove forall quantifiers with the same quantified variable - pcsWithFreshSnapRoot = pcsWithFreshSnapRoot - .map(_.transform { - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body - }(_ => true)) - - val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) - // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFLookup definition - val updatedPcs = snapRhs match { - // Rewrite based on test11 in QPFields.vpr - case SortWrapper(app: App, _) if - app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || - app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => - - def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newTerms = terms.filter(_ match { - case BuiltinEquals(Lookup(_, fvf, at), _) => r.sort == sorts.Ref && fvf == app && r == at - case BuiltinEquals(PredicateLookup(_, psf, args), _) => r.sort == sorts.Snap && psf == app && args == List(r) - case _ => false - }).map { - case BuiltinEquals(lookup, rhs) => - val newLookup = lookup match { - case Lookup(field, fvf, at) => Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) - case PredicateLookup(predname, psf, args) => PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) - } - BuiltinEquals(newLookup, rhs) - } - if (newTerms.isEmpty) return Vector.empty - val quantification = Forall( - Seq(r, freshSnapRoot), - Implies(cond, And(newTerms)), - newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) - } - - conditionalizedPcs.flatMap { - case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) - case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) - case _ => Vector.empty - } - - // Rewrite for test9 in QPFields.vpr - case SortWrapper(lookup, to) if - (lookup.isInstanceOf[Lookup] && - lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && - lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || - (lookup.isInstanceOf[PredicateLookup] && - lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && - lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => - - val app = lookup match { - case Lookup(_, fvf, _) => fvf - case PredicateLookup(_, psf, _) => psf - } - - def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newLhs = mwsfLookup - val newTerms = terms.filter(_ match { - case BuiltinEquals(Lookup(_, fvf, _), rhs) => fvf == app && rhs.contains(freshSnapRoot) - case BuiltinEquals(PredicateLookup(_, psf, _), rhs) => psf == app && rhs.contains(freshSnapRoot) - case _ => false - }).map(t => { - val rhs = t match { case BuiltinEquals(_, rhs) => rhs } - BuiltinEquals(newLhs, SortWrapper(rhs, to)) - }) - if (newTerms.isEmpty) return Vector.empty - val quantification = Forall( - Seq(freshSnapRoot), - Implies(cond, And(newTerms)), - Trigger(newLhs) - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) - } - - conditionalizedPcs.flatMap { - case Implies(cond, And(terms)) => rewriteTerms(cond, terms) - case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) - case _ => Vector.empty - } - - case _ => Vector.empty - } - - // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions - val pcsQuantified = Forall( - freshSnapRoot, - Implies( - And(pcsWithFreshSnapRoot), - BuiltinEquals(mwsfLookup, snapRhs), - ), - Trigger(mwsfLookup) - ) - v1.decider.assumeDefinition(pcsQuantified) - - // Return the summarized path conditions - pcsWithoutFreshSnapRoot ++ updatedPcs :+ pcsQuantified + Success() } def createWandChunkAndRecordResults(s4: State, @@ -423,21 +408,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v4.decider.prover.comment("Definitional axioms for singleton-SM's value") v4.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - - val conservedPcs = summarizeDefinitions( - s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, - freshSnapRoot, mwsf, snapRhs, v4) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) - Success() + appendToRecordedBranches(s5, ch, v4.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v4) }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { - val conservedPcs = summarizeDefinitions( - s5.conservedPcs.head :+ v4.decider.pcs.after(preMark).definitionsOnly, - freshSnapRoot, mwsf, snapRhs, v4) - appendToResults(s5, ch, v4.decider.pcs.after(preMark), conservedPcs, v4) - Success() + appendToRecordedBranches(s5, ch, v4.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v4) }) } } From fc7e824e24689df6f063d393f7decfbdfdde688f Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:32:12 +0200 Subject: [PATCH 29/43] Update silver submodule. --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 4f7bf261d..482e7f2e2 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 4f7bf261d40cfeb3023adf638f62b53f0d48c85c +Subproject commit 482e7f2e2b968f9ec003ad2606ac087c5a1b4401 From bb60e54e9f6a9d994c25f2450abe34b5e4c2408a Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 16 Jun 2024 16:40:54 +0200 Subject: [PATCH 30/43] Rename state and verifier such that their numbers are incrementing in the control flow. --- src/main/scala/rules/MagicWandSupporter.scala | 128 ++++++++---------- 1 file changed, 56 insertions(+), 72 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index ce5411a91..1e4354ca2 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -17,6 +17,7 @@ import viper.silicon.interfaces._ import viper.silicon.interfaces.state._ import viper.silicon.state._ import viper.silicon.state.terms._ +import viper.silicon.utils.ast.BigAnd import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier @@ -199,6 +200,8 @@ object magicWandSupporter extends SymbolicExecutionRules { mwsf: Var, snapRhs: Term, v1: Verifier): Vector[Term] = { + val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) + // Map all path conditions to their conditionalized form and flatten the result val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { case And(terms) => terms @@ -214,7 +217,6 @@ object magicWandSupporter extends SymbolicExecutionRules { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body }(_ => true)) - val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFLookup definition val updatedPcs = snapRhs match { // Rewrite based on test11 in QPFields.vpr @@ -236,13 +238,11 @@ object magicWandSupporter extends SymbolicExecutionRules { BuiltinEquals(newLookup, rhs) } if (newTerms.isEmpty) return Vector.empty - val quantification = Forall( + Vector(Forall( Seq(r, freshSnapRoot), Implies(cond, And(newTerms)), newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) + )) } conditionalizedPcs.flatMap { @@ -266,25 +266,16 @@ object magicWandSupporter extends SymbolicExecutionRules { } def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newLhs = mwsfLookup val newTerms = terms.filter(_ match { case BuiltinEquals(Lookup(_, fvf, _), rhs) => fvf == app && rhs.contains(freshSnapRoot) case BuiltinEquals(PredicateLookup(_, psf, _), rhs) => psf == app && rhs.contains(freshSnapRoot) case _ => false }).map(t => { - val rhs = t match { - case BuiltinEquals(_, rhs) => rhs - } - BuiltinEquals(newLhs, SortWrapper(rhs, to)) + val rhs = t.asInstanceOf[BuiltinEquals].p1 + BuiltinEquals(mwsfLookup, SortWrapper(rhs, to)) }) if (newTerms.isEmpty) return Vector.empty - val quantification = Forall( - Seq(freshSnapRoot), - Implies(cond, And(newTerms)), - Trigger(newLhs) - ) - v1.decider.assumeDefinition(quantification) - Vector(quantification) + Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfLookup))) } conditionalizedPcs.flatMap { @@ -315,9 +306,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * Package a magic wand into a chunk. It performs the computation of the wand's footprint * and captures all values associated to these locations inside the wand's snapshot. * - * {{{ - * package A --* B { } - * }}} + * {{{ package A --* B { } }}} * * For reference see Chapter 3 and 5 of [[http://malte.schwerhoff.de/docs/phd_thesis.pdf Malte Schwerhoff's PhD thesis]] * and [[https://ethz.ch/content/dam/ethz/special-interest/infk/chair-program-method/pm/documents/Education/Theses/Nils_Becker_BA_report.pdf Nils Becker's Bachelor report]] @@ -341,61 +330,52 @@ object magicWandSupporter extends SymbolicExecutionRules { val s = if (state.exhaleExt) state else state.copy(reserveHeaps = Heap() :: state.h :: Nil) - // v.logger.debug(s"wand = $wand") - // v.logger.debug("c.reserveHeaps:") - // s.reserveHeaps.map(v.stateFormatter.format).foreach(str => v.logger.debug(str, 2)) - val stackSize = 3 + s.reserveHeaps.tail.size // IMPORTANT: Size matches structure of reserveHeaps at [State RHS] below var recordedBranches: Seq[(State, Stack[Term], Stack[Option[Exp]], Vector[Term], Chunk)] = Nil - /* TODO: When parallelising branches, some of the runtime assertions in the code below crash - * during some executions - since such crashes are hard to debug, branch parallelisation - * has been disabled for now. - */ - val sEmp = s.copy(h = Heap(), - reserveHeaps = Nil, - exhaleExt = false, - conservedPcs = Vector[RecordedPathConditions]() +: s.conservedPcs, - recordPcs = true, - parallelizeBranches = false) - def appendToRecordedBranches(s5: State, ch: Chunk, pcs: RecordedPathConditions, freshSnapRoot: Var, mwsf: Var, snapRhs: Term, - v4: Verifier): VerificationResult = { + v5: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") // Producing a wand's LHS and executing the packaging proof code can introduce definitional path conditions, e.g. // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, v4) - var conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs - - conservedPcsStack = + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, v5) + val conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs.tail match { - case empty @ Seq() => empty - case head +: tail => (head ++ (s5.conservedPcs.head :+ pcs.definitionsOnly)) +: tail + case empty@Seq() => empty + case head +: tail => + (head ++ (s5.conservedPcs.head :+ pcs.definitionsOnly)) +: tail } - val s6 = s5.copy(conservedPcs = conservedPcsStack, recordPcs = s.recordPcs) + val s6 = s5.copy( + oldHeaps = s.oldHeaps, + parallelizeBranches = s.parallelizeBranches, /* See comment below */ + reserveHeaps = s5.reserveHeaps.drop(3), + conservedPcs = conservedPcsStack, + recordPcs = s.recordPcs, + exhaleExt = false, + ) - recordedBranches :+= (s6, v4.decider.pcs.branchConditions, v4.decider.pcs.branchConditionExps, conservedPcs, ch) + recordedBranches :+= (s6, v5.decider.pcs.branchConditions, v5.decider.pcs.branchConditionExps, conservedPcs, ch) Success() } def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, snapRhs: Term, - v3: Verifier) + v4: Verifier) : VerificationResult = { - val preMark = v3.decider.setPathConditionMark() + val preMark = v4.decider.setPathConditionMark() - v3.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") + v4.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) // If the wand is used as a quantified resource anywhere in the program @@ -403,21 +383,32 @@ object magicWandSupporter extends SymbolicExecutionRules { val bodyVars = wand.subexpressionsToEvaluate(s.program) val formalVars = bodyVars.indices.toList.map(i => Var(Identifier(s"x$i"), v.symbolConverter.toSort(bodyVars(i).typ), false)) - evals(s4, bodyVars, _ => pve, v3)((s5, args, v4) => { - val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, SortWrapper(mwsf, sorts.Snap), v4) - v4.decider.prover.comment("Definitional axioms for singleton-SM's value") - v4.decider.assumeDefinition(smValueDef) + evals(s4, bodyVars, _ => pve, v4)((s5, args, v5) => { + val (sm, smValueDef) = quantifiedChunkSupporter.singletonSnapshotMap(s5, wand, args, SortWrapper(mwsf, sorts.Snap), v5) + v5.decider.prover.comment("Definitional axioms for singleton-SM's value") + v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v4.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v4) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v5) }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) - this.createChunk(s4, wand, wandSnapshot, pve, v3)((s5, ch, v4) => { - appendToRecordedBranches(s5, ch, v4.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v4) + this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v5) }) } } + /* TODO: When parallelising branches, some of the runtime assertions in the code below crash + * during some executions - since such crashes are hard to debug, branch parallelisation + * has been disabled for now. + */ + val sEmp = s.copy(h = Heap(), + reserveHeaps = Nil, + exhaleExt = false, + conservedPcs = Vector[RecordedPathConditions]() +: s.conservedPcs, + recordPcs = true, + parallelizeBranches = false) + val tempResult = executionFlowController.locally(sEmp, v)((s1, v1) => { /* A snapshot (binary tree) will be constructed using First/Second datatypes, * that preserves the original root. The leafs of this tree will later appear @@ -458,16 +449,13 @@ object magicWandSupporter extends SymbolicExecutionRules { // Execute proof script, i.e. the part written after the magic wand wrapped by curly braces. // The proof script should transform the current state such that we can consume the wand's RHS. - executor.exec(s2, proofScriptCfg, v2)((proofScriptState, proofScriptVerifier) => { + executor.exec(s2, proofScriptCfg, v2)((sProofScript, v3) => { // Consume the wand's RHS and produce a snapshot which records all the values of variables on the RHS. // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. - consume( - proofScriptState.copy(oldHeaps = s2.oldHeaps, reserveCfgs = proofScriptState.reserveCfgs.tail), - wand.right, pve, proofScriptVerifier - )((s3, snapRhs, v3) => { - - createWandChunkAndRecordResults(s3.copy(exhaleExt = false, oldHeaps = s.oldHeaps), freshSnapRoot, snapRhs, v3) - }) + val s3 = sProofScript.copy(oldHeaps = s2.oldHeaps, reserveCfgs = sProofScript.reserveCfgs.tail) + consume(s3, wand.right, pve, v3)((s4, snapRhs, v4) => + createWandChunkAndRecordResults(s4, freshSnapRoot, snapRhs, v4) + ) }) }) }) @@ -480,24 +468,20 @@ object magicWandSupporter extends SymbolicExecutionRules { createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) } - recordedBranches.foldLeft(tempResult)((prevRes, recordedState) => { + recordedBranches.foldLeft(tempResult)((prevRes, recordedBranch) => { prevRes && { - val (state, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedState - val s1 = state.copy( - reserveHeaps = state.reserveHeaps.drop(3), - parallelizeBranches = s.parallelizeBranches /* See comment above */ - ) + val (s6, branchConditions, branchConditionsExp, conservedPcs, magicWandChunk) = recordedBranch // We execute the continuation Q in a new scope with all branch conditions and all conserved path conditions. - executionFlowController.locally(s1, v)((s2, v1) => { + executionFlowController.locally(s6, v)((s7, v7) => { // Set the branch conditions - v1.decider.setCurrentBranchCondition(And(branchConditions), Some(viper.silicon.utils.ast.BigAnd(branchConditionsExp.flatten))) + v7.decider.setCurrentBranchCondition(And(branchConditions), Some(BigAnd(branchConditionsExp.flatten))) // Recreate all path conditions in the Z3 proof script that we recorded for that branch - conservedPcs.foreach(pcs => v1.decider.assume(pcs)) + conservedPcs.foreach(pcs => v7.decider.assume(pcs)) // Execute the continuation Q - Q(s2, magicWandChunk, v1) + Q(s7, magicWandChunk, v7) }) } }) From 064b3c3b6611924dcbbbf41298278545eab45c2e Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:52:50 +0200 Subject: [PATCH 31/43] Simplify conversion of snapWand from Snap to MWSF. --- src/main/scala/rules/MagicWandSupporter.scala | 54 ++++++++----------- src/main/scala/state/Terms.scala | 9 ---- src/main/scala/state/Utils.scala | 2 +- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 1e4354ca2..98856a16f 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -225,18 +225,15 @@ object magicWandSupporter extends SymbolicExecutionRules { app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newTerms = terms.filter(_ match { - case BuiltinEquals(Lookup(_, fvf, at), _) => r.sort == sorts.Ref && fvf == app && r == at - case BuiltinEquals(PredicateLookup(_, psf, args), _) => r.sort == sorts.Snap && psf == app && args == List(r) - case _ => false - }).map { - case BuiltinEquals(lookup, rhs) => - val newLookup = lookup match { - case Lookup(field, fvf, at) => Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) - case PredicateLookup(predname, psf, args) => PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) - } - BuiltinEquals(newLookup, rhs) - } + val newTerms = terms.map { + case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => + val newLookup = Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) + Some(BuiltinEquals(newLookup, rhs)) + case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => + val newLookup = PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) + Some(BuiltinEquals(newLookup, rhs)) + case _ => None + }.filter(_.isDefined).map(_.get) if (newTerms.isEmpty) return Vector.empty Vector(Forall( Seq(r, freshSnapRoot), @@ -266,14 +263,13 @@ object magicWandSupporter extends SymbolicExecutionRules { } def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newTerms = terms.filter(_ match { - case BuiltinEquals(Lookup(_, fvf, _), rhs) => fvf == app && rhs.contains(freshSnapRoot) - case BuiltinEquals(PredicateLookup(_, psf, _), rhs) => psf == app && rhs.contains(freshSnapRoot) - case _ => false - }).map(t => { - val rhs = t.asInstanceOf[BuiltinEquals].p1 - BuiltinEquals(mwsfLookup, SortWrapper(rhs, to)) - }) + val newTerms = terms.map { + case BuiltinEquals(Lookup(_, fvf, _), rhs) if fvf == app && rhs.contains(freshSnapRoot) => + Some(BuiltinEquals(mwsfLookup, SortWrapper(rhs, to))) + case BuiltinEquals(PredicateLookup(_, psf, _), rhs) if psf == app && rhs.contains(freshSnapRoot) => + Some(BuiltinEquals(mwsfLookup, SortWrapper(rhs, to))) + case _ => None + }.filter(_.isDefined).map(_.get) if (newTerms.isEmpty) return Vector.empty Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfLookup))) } @@ -512,24 +508,20 @@ object magicWandSupporter extends SymbolicExecutionRules { * The old solution in this case did use this assumption: * v2.decider.assume(snap === snapWand.abstractLhs) */ - assert(snapLhs.sort == sorts.Snap, s"expected snapshot but found: $snapLhs") + assert(snapLhs.sort == sorts.Snap, s"Expected snapshot but found: $snapLhs") // Create copy of the state with a new labelled heap (i.e. `oldHeaps`) called "lhs". val s3 = s2.copy(oldHeaps = s1.oldHeaps + (Verifier.MAGIC_WAND_LHS_STATE_LABEL -> this.getEvalHeap(s1))) - // If the snapWand is a (wrapped) MagicWandSnapshot then lookup the snapshot of the right-hand side by applying snapLhs. - val magicWandSnapshotLookup = snapWand match { - case snapshot: MagicWandSnapshot => snapshot.applyToMWSF(snapLhs) - case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.applyToMWSF(snapLhs) - case predicateLookup: PredicateLookup => - MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction), snapLhs) - case SortWrapper(predicateLookup: PredicateLookup, _) => - MWSFLookup(SortWrapper(predicateLookup, sorts.MagicWandSnapFunction), snapLhs) - case _ => snapWand + // Convert snapWand to MWSF + val mwsf = snapWand match { + case SortWrapper(mwsf: MagicWandSnapshot, _) => mwsf + case SortWrapper(snapshot, _) => SortWrapper(snapshot, sorts.MagicWandSnapFunction) + case _ => SortWrapper(snapWand, sorts.MagicWandSnapFunction) } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(magicWandSnapshotLookup), wand.right, pve, v2)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(MWSFLookup(mwsf, snapLhs)), wand.right, pve, v2)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index e08f4854b..03855e152 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2315,15 +2315,6 @@ class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[T override lazy val toString = s"wandSnap($mwsf)" override val equalityDefiningMembers: Term = mwsf - - /** - * Apply the given snapshot of the left-hand side to the magic wand map to get the snapshot of the right-hand side - * which includes the values of the left-hand side. - * - * @param snapLhs The snapshot of the left-hand side that should be applied to the magic wand map. - * @return The snapshot of the right-hand side that preserves the values of the left-hand side. - */ - def applyToMWSF(snapLhs: Term): Term = MWSFLookup(mwsf, snapLhs) } object MagicWandSnapshot extends PreciseCondFlyweightFactory[Term, MagicWandSnapshot] { diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index d2667e8cd..0ea98e849 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -91,7 +91,7 @@ package object utils { case PredicatePermLookup(_, pm, args) => Seq(pm) ++ args case FieldTrigger(_, fvf, at) => fvf :: at :: Nil case PredicateTrigger(_, psf, args) => psf +: args - + case MagicWandSnapshot(mwsf) => mwsf :: Nil } /** @see [[viper.silver.ast.utility.Simplifier.simplify]] */ From 5a5a6afb781155628cc46d6cf310f639c41cd76b Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:09:36 +0200 Subject: [PATCH 32/43] Rename MWSFLookup to MWSFApply. --- .../decider/TermToSMTLib2Converter.scala | 2 +- .../scala/decider/TermToZ3APIConverter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 20 +++++++++---------- src/main/scala/state/Terms.scala | 12 +++++------ src/main/scala/state/Utils.scala | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index d876d1db7..6d278a8dd 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -316,7 +316,7 @@ class TermToSMTLib2Converter parens(text("let") <+> parens(docBindings) <+> render(body)) case MagicWandSnapshot(mwsf) => render(mwsf) - case MWSFLookup(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) + case MWSFApply(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm | _: Quantification => diff --git a/src/main/scala/decider/TermToZ3APIConverter.scala b/src/main/scala/decider/TermToZ3APIConverter.scala index 152bf1d64..b3d620f84 100644 --- a/src/main/scala/decider/TermToZ3APIConverter.scala +++ b/src/main/scala/decider/TermToZ3APIConverter.scala @@ -445,7 +445,7 @@ class TermToZ3APIConverter case Let(bindings, body) => convert(body.replace(bindings)) - case MWSFLookup(mwsf, snap) => createApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) + case MWSFApply(mwsf, snap) => createApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm | _: Quantification => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 98856a16f..f69db018e 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -200,7 +200,7 @@ object magicWandSupporter extends SymbolicExecutionRules { mwsf: Var, snapRhs: Term, v1: Verifier): Vector[Term] = { - val mwsfLookup = MWSFLookup(mwsf, freshSnapRoot) + val mwsfApply = MWSFApply(mwsf, freshSnapRoot) // Map all path conditions to their conditionalized form and flatten the result val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { @@ -217,7 +217,7 @@ object magicWandSupporter extends SymbolicExecutionRules { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body }(_ => true)) - // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFLookup definition + // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFApply definition val updatedPcs = snapRhs match { // Rewrite based on test11 in QPFields.vpr case SortWrapper(app: App, _) if @@ -227,10 +227,10 @@ object magicWandSupporter extends SymbolicExecutionRules { def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { val newTerms = terms.map { case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => - val newLookup = Lookup(field, SortWrapper(mwsfLookup, fvf.sort), at) + val newLookup = Lookup(field, SortWrapper(mwsfApply, fvf.sort), at) Some(BuiltinEquals(newLookup, rhs)) case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => - val newLookup = PredicateLookup(predname, SortWrapper(mwsfLookup, psf.sort), args) + val newLookup = PredicateLookup(predname, SortWrapper(mwsfApply, psf.sort), args) Some(BuiltinEquals(newLookup, rhs)) case _ => None }.filter(_.isDefined).map(_.get) @@ -265,13 +265,13 @@ object magicWandSupporter extends SymbolicExecutionRules { def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { val newTerms = terms.map { case BuiltinEquals(Lookup(_, fvf, _), rhs) if fvf == app && rhs.contains(freshSnapRoot) => - Some(BuiltinEquals(mwsfLookup, SortWrapper(rhs, to))) + Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) case BuiltinEquals(PredicateLookup(_, psf, _), rhs) if psf == app && rhs.contains(freshSnapRoot) => - Some(BuiltinEquals(mwsfLookup, SortWrapper(rhs, to))) + Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) case _ => None }.filter(_.isDefined).map(_.get) if (newTerms.isEmpty) return Vector.empty - Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfLookup))) + Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) } conditionalizedPcs.flatMap { @@ -288,9 +288,9 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot, Implies( And(pcsWithFreshSnapRoot), - BuiltinEquals(mwsfLookup, snapRhs), + BuiltinEquals(mwsfApply, snapRhs), ), - Trigger(mwsfLookup) + Trigger(mwsfApply) ) v1.decider.assumeDefinition(pcsQuantified) @@ -521,7 +521,7 @@ object magicWandSupporter extends SymbolicExecutionRules { } // Produce the wand's RHS. - produce(s3.copy(conservingSnapshotGeneration = true), toSf(MWSFLookup(mwsf, snapLhs)), wand.right, pve, v2)((s4, v3) => { + produce(s3.copy(conservingSnapshotGeneration = true), toSf(MWSFApply(mwsf, snapLhs)), wand.right, pve, v2)((s4, v3) => { // Recreate old state without the magic wand, and the state with the oldHeap called lhs. val s5 = s4.copy(g = s1.g, conservingSnapshotGeneration = s3.conservingSnapshotGeneration) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 03855e152..9b0d7516a 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2330,24 +2330,24 @@ object MagicWandSnapshot extends PreciseCondFlyweightFactory[Term, MagicWandSnap * @param mwsf Term of sort [[sorts.MagicWandSnapFunction]]. Function from `Snap` to `Snap`. * @param snap Term of sort [[sorts.Snap]] to which the MWSF is applied to. It represents the values of the wand's LHS. */ -class MWSFLookup(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFLookup] { +class MWSFApply(val mwsf: Term, val snap: Term) extends Term with ConditionalFlyweightBinaryOp[MWSFApply] { val sort: Sort = sorts.Snap override def p0: Term = mwsf override def p1: Term = snap override lazy val toString = s"$mwsf[$snap]" } -object MWSFLookup extends PreciseCondFlyweightFactory[(Term, Term), MWSFLookup] { - override def apply(pair: (Term, Term)): MWSFLookup = { +object MWSFApply extends PreciseCondFlyweightFactory[(Term, Term), MWSFApply] { + override def apply(pair: (Term, Term)): MWSFApply = { val (mwsf, snap) = pair utils.assertSort(mwsf, "mwsf", sorts.MagicWandSnapFunction) utils.assertSort(snap, "snap", sorts.Snap) createIfNonExistent(pair) } - /** Create an instance of [[viper.silicon.state.terms.MWSFLookup]]. */ - override def actualCreate(args: (Term, Term)): MWSFLookup = - new MWSFLookup(args._1, args._2) + /** Create an instance of [[viper.silicon.state.terms.MWSFApply]]. */ + override def actualCreate(args: (Term, Term)): MWSFApply = + new MWSFApply(args._1, args._2) } class MagicWandChunkTerm(val chunk: MagicWandChunk) extends Term with ConditionalFlyweight[MagicWandChunk, MagicWandChunkTerm] { diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index 0ea98e849..80e9d32d0 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -197,7 +197,7 @@ package object utils { case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) - case MWSFLookup(t0, t1) => MWSFLookup(go(t0), go(t1)) + case MWSFApply(t0, t1) => MWSFApply(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) case Second(t) => Second(go(t)) From 10942e3354c8ebaa8f021b8b5dec86082142bc5c Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:33:31 +0200 Subject: [PATCH 33/43] Write documentation about optimizations. --- src/main/scala/rules/MagicWandSupporter.scala | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index f69db018e..96f4d5f0c 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -219,7 +219,17 @@ object magicWandSupporter extends SymbolicExecutionRules { // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFApply definition val updatedPcs = snapRhs match { - // Rewrite based on test11 in QPFields.vpr + /* + * Rewrite based on test11 in QPFields.vpr + * --------------------------------------- + * Assume we have a mwsf and fvf with following definition: + * ∀ t: Snap :: mwsf.apply(t) == fvf + * and the path condition: + * ∀ r: Ref :: cond ==> fvf.lookup(r) == rhs + * then we can add the path condition to: + * ∀ r: Ref, t: Snap :: cond ==> mwsf.apply(t).lookup(r) == rhs + */ + // TODO Extend this for cases where the snapRhs is a binary tree case SortWrapper(app: App, _) if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => @@ -248,7 +258,16 @@ object magicWandSupporter extends SymbolicExecutionRules { case _ => Vector.empty } - // Rewrite for test9 in QPFields.vpr + /* + * Rewrite based on test9 in QPFields.vpr + * --------------------------------------- + * Assume we have a mwsf, a fvf, and some reference ref with following definition: + * ∀ t: Snap :: mwsf.apply(t) == fvf.lookup(ref) + * and the path condition: + * cond ==> fvf.lookup(ref) == rhs + * then we can add the path condition to: + * ∀ t: Snap :: cond ==> mwsf.apply(t) == rhs + */ case SortWrapper(lookup, to) if (lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && @@ -264,9 +283,11 @@ object magicWandSupporter extends SymbolicExecutionRules { def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { val newTerms = terms.map { - case BuiltinEquals(Lookup(_, fvf, _), rhs) if fvf == app && rhs.contains(freshSnapRoot) => + case BuiltinEquals(Lookup(_, fvf, at), rhs) + if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) - case BuiltinEquals(PredicateLookup(_, psf, _), rhs) if psf == app && rhs.contains(freshSnapRoot) => + case BuiltinEquals(PredicateLookup(_, psf, args), rhs) + if psf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args => Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) case _ => None }.filter(_.isDefined).map(_.get) From d5d7591b3b102f0234f766b45b9801ea24206d56 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:52:10 +0200 Subject: [PATCH 34/43] Quantify overall FVFs and PSFs. --- silver | 2 +- src/main/scala/decider/ProverStdIO.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 177 ++++++++++++------ src/main/scala/state/Identifiers.scala | 6 + 4 files changed, 126 insertions(+), 61 deletions(-) diff --git a/silver b/silver index 482e7f2e2..7a0b3458d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 482e7f2e2b968f9ec003ad2606ac087c5a1b4401 +Subproject commit 7a0b3458dd871d7d98945a2f77d9066c85c5d45d diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 585bf2235..9633b5298 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -457,7 +457,7 @@ abstract class ProverStdIO(uniqueId: String, if (warning) { val msg = s"Prover warning: $result" reporter report InternalWarningMessage(msg) - logger warn msg + if (result.endsWith("pattern does not contain all quantified variables.")) logger info msg else logger warn msg } // When `smt.qi.profile` is `true`, Z3 periodically reports the quantifier instantiations using the format diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 96f4d5f0c..f54f86504 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -199,55 +199,112 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, mwsf: Var, snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { val mwsfApply = MWSFApply(mwsf, freshSnapRoot) + val freshSnapshotMaps = (v1.decider.freshFunctions -- functionsBeforePackaging) + .filter(f => f.func.resultSort.isInstanceOf[sorts.FieldValueFunction] || f.func.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) // Map all path conditions to their conditionalized form and flatten the result - val conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { + var conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { case And(terms) => terms case term => Vector(term) } + // Identify whether two or three of the snapshot maps are equal + val groups = conditionalizedPcs.collect { + // Extract names of all FVFs and PSFs that are part of an equality of the form: fvf0 == fvf1 or psf0 == psf1 + case BuiltinEquals(App(fvf0, Seq()), App(fvf1, Seq())) + if fvf0.resultSort.isInstanceOf[sorts.FieldValueFunction] && fvf1.resultSort.isInstanceOf[sorts.FieldValueFunction] + => (fvf0.id.name, fvf1.id.name) + case BuiltinEquals(App(psf0, Seq()), App(psf1, Seq())) + if psf0.resultSort.isInstanceOf[sorts.PredicateSnapFunction] && psf1.resultSort.isInstanceOf[sorts.PredicateSnapFunction] + => (psf0.id.name, psf1.id.name) + }.foldLeft( + freshSnapshotMaps.map(f => (f.func.id.name, f.func.id.name)).toMap + )((assignments, eq) => { + // Find all groups of equal snapshot maps + val (a, b) = eq + if (!assignments.contains(a) || !assignments.contains(b)) { + assignments + } else { + val assA = assignments(a) + val assB = assignments(b) + if (assA == assB) { + assignments + } else if (assA < assB) { + assignments.map { + case (x, y) => if (y == assB) (x, assA) else (x, y) + } + } else { + assignments.map { + case (x, y) => if (y == assA) (x, assB) else (x, y) + } + } + } + }) + + // Replace all snapshot maps with one snapshot map of the group they belong to + def replaceVars(t: Term): Term = t.transform({ + case App(fun: Fun, Seq()) if + (fun.resultSort.isInstanceOf[sorts.FieldValueFunction] || fun.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) + && groups.contains(fun.id.name) => + App(fun.copy(id = Identifier(groups(fun.id.name))), Seq()) + })(_ => true) + conditionalizedPcs = conditionalizedPcs.map(replaceVars) + val snapRhsUpdated = replaceVars(snapRhs) + + // Combine all variables that will be quantified over + var quantifiedVars = freshSnapRoot +: freshSnapshotMaps.filter(f => { + val id = f.func.id.name + groups.exists({ case (from, to) => from == to && from == id }) + }).map(f => App(f.func, Seq.empty)).toSeq + // Partition path conditions into a set which include the freshSnapRoot and those which do not - var (pcsWithFreshSnapRoot, pcsWithoutFreshSnapRoot) = conditionalizedPcs.partition(pcs => pcs.contains(freshSnapRoot)) + var (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = conditionalizedPcs.partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) - // Remove forall quantifiers with the same quantified variable - pcsWithFreshSnapRoot = pcsWithFreshSnapRoot + // Remove forall quantifiers with the quantified variable in `quantifiedVars` + pcsWithQuantifiedVars = pcsWithQuantifiedVars .map(_.transform { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.sort == sorts.Ref || v.sort == sorts.Snap => { + if (!quantifiedVars.contains(v)) quantifiedVars :+= v + body + } }(_ => true)) + val quantifiers = quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)) - // If the snapRhs is a FieldValueFunction or PredicateSnapFunction, substitute the snapRhs with the MWSFApply definition - val updatedPcs = snapRhs match { + // If the `snapRhs` is a FVF or PSF, substitute the snapRhs with the MWSFApply definition + val updatedPcs = snapRhsUpdated match { /* * Rewrite based on test11 in QPFields.vpr * --------------------------------------- * Assume we have a mwsf and fvf with following definition: - * ∀ t: Snap :: mwsf.apply(t) == fvf - * and the path condition: - * ∀ r: Ref :: cond ==> fvf.lookup(r) == rhs + * ∀ t: Snap :: cond1 ==> mwsf.apply(t) == fvf + * and the path condition is part of cond1: + * ∀ r: Ref :: cond2 ==> fvf.lookup(r) == rhs * then we can add the path condition to: - * ∀ r: Ref, t: Snap :: cond ==> mwsf.apply(t).lookup(r) == rhs + * ∀ r: Ref, t: Snap :: cond1 ==> (mwsf.apply(t) == fvf && cond2 ==> mwsf.apply(t).lookup(r) == rhs) */ // TODO Extend this for cases where the snapRhs is a binary tree case SortWrapper(app: App, _) if app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => - def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newTerms = terms.map { + def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Term] = { + val newTerms = terms.collect { case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => val newLookup = Lookup(field, SortWrapper(mwsfApply, fvf.sort), at) - Some(BuiltinEquals(newLookup, rhs)) + BuiltinEquals(newLookup, rhs) case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => val newLookup = PredicateLookup(predname, SortWrapper(mwsfApply, psf.sort), args) - Some(BuiltinEquals(newLookup, rhs)) - case _ => None - }.filter(_.isDefined).map(_.get) + BuiltinEquals(newLookup, rhs) + } if (newTerms.isEmpty) return Vector.empty + // Vector(Implies(cond, And(newTerms))) Vector(Forall( - Seq(r, freshSnapRoot), - Implies(cond, And(newTerms)), + quantifiers, + Implies(And(cond +: pcsWithQuantifiedVars), And(newTerms)), newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq )) } @@ -268,55 +325,54 @@ object magicWandSupporter extends SymbolicExecutionRules { * then we can add the path condition to: * ∀ t: Snap :: cond ==> mwsf.apply(t) == rhs */ - case SortWrapper(lookup, to) if - (lookup.isInstanceOf[Lookup] && - lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && - lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || - (lookup.isInstanceOf[PredicateLookup] && - lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && - lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => - - val app = lookup match { - case Lookup(_, fvf, _) => fvf - case PredicateLookup(_, psf, _) => psf - } - - def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - val newTerms = terms.map { - case BuiltinEquals(Lookup(_, fvf, at), rhs) - if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => - Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) - case BuiltinEquals(PredicateLookup(_, psf, args), rhs) - if psf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args => - Some(BuiltinEquals(mwsfApply, SortWrapper(rhs, to))) - case _ => None - }.filter(_.isDefined).map(_.get) - if (newTerms.isEmpty) return Vector.empty - Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) - } - - conditionalizedPcs.flatMap { - case Implies(cond, And(terms)) => rewriteTerms(cond, terms) - case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) - case _ => Vector.empty - } + // case SortWrapper(lookup, to) if + // (lookup.isInstanceOf[Lookup] && + // lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && + // lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || + // (lookup.isInstanceOf[PredicateLookup] && + // lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && + // lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => + // + // val app = lookup match { + // case Lookup(_, fvf, _) => fvf + // case PredicateLookup(_, psf, _) => psf + // } + // + // def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + // val newTerms = terms.collect { + // case BuiltinEquals(Lookup(_, fvf, at), rhs) + // if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => + // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) + // case BuiltinEquals(PredicateLookup(_, psf, args), rhs) + // if psf == app && rhs.contains(freshSnapRoot) /* && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args */ => + // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) + // } + // if (newTerms.isEmpty) return Vector.empty + // Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) + // } + // + // conditionalizedPcs.flatMap { + // case Implies(cond, And(terms)) => rewriteTerms(cond, terms) + // case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) + // case _ => Vector.empty + // } case _ => Vector.empty } // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions val pcsQuantified = Forall( - freshSnapRoot, + quantifiers, Implies( - And(pcsWithFreshSnapRoot), - BuiltinEquals(mwsfApply, snapRhs), + And(pcsWithQuantifiedVars), + BuiltinEquals(mwsfApply, snapRhsUpdated) ), Trigger(mwsfApply) ) v1.decider.assumeDefinition(pcsQuantified) // Return the summarized path conditions - pcsWithoutFreshSnapRoot ++ updatedPcs :+ pcsQuantified + pcsWithoutQuantifiedVars ++ updatedPcs :+ pcsQuantified } /** @@ -357,6 +413,7 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, mwsf: Var, snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], v5: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") @@ -364,7 +421,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, v5) + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) val conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs.tail match { case empty@Seq() => empty @@ -388,6 +445,7 @@ object magicWandSupporter extends SymbolicExecutionRules { def createWandChunkAndRecordResults(s4: State, freshSnapRoot: Var, snapRhs: Term, + functionsBeforePackaging: Set[FunctionDecl], v4: Verifier) : VerificationResult = { val preMark = v4.decider.setPathConditionMark() @@ -405,12 +463,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v5.decider.prover.comment("Definitional axioms for singleton-SM's value") v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) }) } } @@ -434,6 +492,7 @@ object magicWandSupporter extends SymbolicExecutionRules { * applying the wand preserves values from the LHS into the RHS. */ val freshSnapRoot = freshSnap(sorts.Snap, v1) + val functionsBeforePackaging = v1.decider.freshFunctions // Produce the wand's LHS. produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1)((sLhs, v2) => { @@ -471,7 +530,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. val s3 = sProofScript.copy(oldHeaps = s2.oldHeaps, reserveCfgs = sProofScript.reserveCfgs.tail) consume(s3, wand.right, pve, v3)((s4, snapRhs, v4) => - createWandChunkAndRecordResults(s4, freshSnapRoot, snapRhs, v4) + createWandChunkAndRecordResults(s4, freshSnapRoot, snapRhs, functionsBeforePackaging, v4) ) }) }) @@ -482,7 +541,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v.decider.freshFunctions, v) } recordedBranches.foldLeft(tempResult)((prevRes, recordedBranch) => { diff --git a/src/main/scala/state/Identifiers.scala b/src/main/scala/state/Identifiers.scala index 6857c7ee9..a4de3e1d5 100644 --- a/src/main/scala/state/Identifiers.scala +++ b/src/main/scala/state/Identifiers.scala @@ -18,6 +18,12 @@ sealed trait Identifier { withSuffix(Identifier.defaultSeparator, suffix) def withSuffix(separator: String, suffix: String): SuffixedIdentifier + + override def equals(obj: Any): Boolean = + obj match { + case that: Identifier => this.toString == that.toString + case _ => false + } } /* TODO: Remove object Identifier, make concrete identifiers' constructors private, and force all From 957ed9d7f550b2d437c1bb008bba69908959693c Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:12:05 +0200 Subject: [PATCH 35/43] Generalize heuristic for quantification for snapshot trees. --- src/main/scala/rules/MagicWandSupporter.scala | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index f54f86504..b8c40260a 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -274,91 +274,91 @@ object magicWandSupporter extends SymbolicExecutionRules { }(_ => true)) val quantifiers = quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)) - // If the `snapRhs` is a FVF or PSF, substitute the snapRhs with the MWSFApply definition - val updatedPcs = snapRhsUpdated match { - /* - * Rewrite based on test11 in QPFields.vpr - * --------------------------------------- - * Assume we have a mwsf and fvf with following definition: - * ∀ t: Snap :: cond1 ==> mwsf.apply(t) == fvf - * and the path condition is part of cond1: - * ∀ r: Ref :: cond2 ==> fvf.lookup(r) == rhs - * then we can add the path condition to: - * ∀ r: Ref, t: Snap :: cond1 ==> (mwsf.apply(t) == fvf && cond2 ==> mwsf.apply(t).lookup(r) == rhs) - */ - // TODO Extend this for cases where the snapRhs is a binary tree - case SortWrapper(app: App, _) if - app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || - app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => - - def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Term] = { - val newTerms = terms.collect { - case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => - val newLookup = Lookup(field, SortWrapper(mwsfApply, fvf.sort), at) - BuiltinEquals(newLookup, rhs) - case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => - val newLookup = PredicateLookup(predname, SortWrapper(mwsfApply, psf.sort), args) - BuiltinEquals(newLookup, rhs) + /* + * Rewrite based on test11 in QPFields.vpr + * --------------------------------------- + * Assume we have a MWSF and FVF with the following definition: + * ∀ t: Snap, freshSMs :: cond1 ==> mwsf.apply(t) == fvf + * and the following path condition that is part of cond1: + * ∀ r: Ref :: cond2 ==> fvf.lookup(r) == rhs + * Then we create the path condition: + * ∀ r: Ref, t: Snap, freshSMs :: cond1 ==> (cond2 ==> mwsf.apply(t).lookup(r) == rhs) + */ + def simplifyQuantification(snap: Term, to: Term): Vector[Term] = { + snap match { + case SortWrapper(app: App, _) if + app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || + app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => + + def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Term] = { + val newTerms = terms.collect { + case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => + val newLookup = Lookup(field, SortWrapper(mwsfApply, fvf.sort), at) + BuiltinEquals(newLookup, rhs) + case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => + val newLookup = PredicateLookup(predname, SortWrapper(mwsfApply, psf.sort), args) + BuiltinEquals(newLookup, rhs) + } + if (newTerms.isEmpty) return Vector.empty + Vector(Forall( + quantifiers, + Implies(And(cond +: pcsWithQuantifiedVars), And(newTerms)), + newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq + )) } - if (newTerms.isEmpty) return Vector.empty - // Vector(Implies(cond, And(newTerms))) - Vector(Forall( - quantifiers, - Implies(And(cond +: pcsWithQuantifiedVars), And(newTerms)), - newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq - )) - } - - conditionalizedPcs.flatMap { - case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) - case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) - case _ => Vector.empty - } - /* - * Rewrite based on test9 in QPFields.vpr - * --------------------------------------- - * Assume we have a mwsf, a fvf, and some reference ref with following definition: - * ∀ t: Snap :: mwsf.apply(t) == fvf.lookup(ref) - * and the path condition: - * cond ==> fvf.lookup(ref) == rhs - * then we can add the path condition to: - * ∀ t: Snap :: cond ==> mwsf.apply(t) == rhs - */ - // case SortWrapper(lookup, to) if - // (lookup.isInstanceOf[Lookup] && - // lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && - // lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || - // (lookup.isInstanceOf[PredicateLookup] && - // lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && - // lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => - // - // val app = lookup match { - // case Lookup(_, fvf, _) => fvf - // case PredicateLookup(_, psf, _) => psf - // } - // - // def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - // val newTerms = terms.collect { - // case BuiltinEquals(Lookup(_, fvf, at), rhs) - // if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => - // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) - // case BuiltinEquals(PredicateLookup(_, psf, args), rhs) - // if psf == app && rhs.contains(freshSnapRoot) /* && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args */ => - // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) - // } - // if (newTerms.isEmpty) return Vector.empty - // Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) - // } - // - // conditionalizedPcs.flatMap { - // case Implies(cond, And(terms)) => rewriteTerms(cond, terms) - // case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) - // case _ => Vector.empty - // } - - case _ => Vector.empty + conditionalizedPcs.flatMap { + case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) + case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) + case _ => Vector.empty + } + case Combine(snap1, snap2) => simplifyQuantification(snap1, First(to)) ++ simplifyQuantification(snap2, Second(to)) + case _ => Vector.empty + } } + val pcsUpdated = simplifyQuantification(snapRhsUpdated, mwsfApply) + + /* + * Rewrite based on test9 in QPFields.vpr + * --------------------------------------- + * Assume we have a mwsf, a fvf, and some reference ref with following definition: + * ∀ t: Snap :: mwsf.apply(t) == fvf.lookup(ref) + * and the path condition: + * cond ==> fvf.lookup(ref) == rhs + * then we can add the path condition to: + * ∀ t: Snap :: cond ==> mwsf.apply(t) == rhs + */ + // case SortWrapper(lookup, to) if + // (lookup.isInstanceOf[Lookup] && + // lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && + // lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || + // (lookup.isInstanceOf[PredicateLookup] && + // lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && + // lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => + // + // val app = lookup match { + // case Lookup(_, fvf, _) => fvf + // case PredicateLookup(_, psf, _) => psf + // } + // + // def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { + // val newTerms = terms.collect { + // case BuiltinEquals(Lookup(_, fvf, at), rhs) + // if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => + // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) + // case BuiltinEquals(PredicateLookup(_, psf, args), rhs) + // if psf == app && rhs.contains(freshSnapRoot) /* && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args */ => + // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) + // } + // if (newTerms.isEmpty) return Vector.empty + // Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) + // } + // + // conditionalizedPcs.flatMap { + // case Implies(cond, And(terms)) => rewriteTerms(cond, terms) + // case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) + // case _ => Vector.empty + // } // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions val pcsQuantified = Forall( @@ -372,7 +372,7 @@ object magicWandSupporter extends SymbolicExecutionRules { v1.decider.assumeDefinition(pcsQuantified) // Return the summarized path conditions - pcsWithoutQuantifiedVars ++ updatedPcs :+ pcsQuantified + pcsWithoutQuantifiedVars ++ pcsUpdated :+ pcsQuantified } /** From 94cf819592dfd478beb7efb58246b54f6cfd21fb Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:26:17 +0200 Subject: [PATCH 36/43] Implement MWSF which uses an image function to define when the definition is valid. --- silver | 2 +- src/main/scala/decider/ProverStdIO.scala | 2 +- .../decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 170 +++++++----------- src/main/scala/rules/Producer.scala | 9 +- src/main/scala/state/Terms.scala | 12 +- src/main/scala/state/Utils.scala | 4 +- 8 files changed, 84 insertions(+), 119 deletions(-) diff --git a/silver b/silver index 7a0b3458d..c29583793 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 7a0b3458dd871d7d98945a2f77d9066c85c5d45d +Subproject commit c295837930686bea707174164bcb2cd0a8689a67 diff --git a/src/main/scala/decider/ProverStdIO.scala b/src/main/scala/decider/ProverStdIO.scala index 9633b5298..585bf2235 100644 --- a/src/main/scala/decider/ProverStdIO.scala +++ b/src/main/scala/decider/ProverStdIO.scala @@ -457,7 +457,7 @@ abstract class ProverStdIO(uniqueId: String, if (warning) { val msg = s"Prover warning: $result" reporter report InternalWarningMessage(msg) - if (result.endsWith("pattern does not contain all quantified variables.")) logger info msg else logger warn msg + logger warn msg } // When `smt.qi.profile` is `true`, Z3 periodically reports the quantifier instantiations using the format diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index 6d278a8dd..b511f7d7e 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -315,7 +315,7 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(mwsf) => render(mwsf) + case MagicWandSnapshot(mwsf, _) => render(mwsf) case MWSFApply(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index d146c72ee..ec5adefd4 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -185,7 +185,7 @@ object havocSupporter extends SymbolicExecutionRules { case ch: MagicWandChunk => val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val cond = replacementCond(lhs, ch.args, condInfo) - val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) + val magicWandSnapshot = MagicWandSnapshot((Ite(cond, havockedSnap, ch.snap.mwsf), ch.snap.imgFun)) ch.withSnap(magicWandSnapshot) case ch => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index b8c40260a..6ca5fa214 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -199,6 +199,7 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, mwsf: Var, snapRhs: Term, + imgFun: Function, functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { val mwsfApply = MWSFApply(mwsf, freshSnapRoot) @@ -250,129 +251,82 @@ object magicWandSupporter extends SymbolicExecutionRules { (fun.resultSort.isInstanceOf[sorts.FieldValueFunction] || fun.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) && groups.contains(fun.id.name) => App(fun.copy(id = Identifier(groups(fun.id.name))), Seq()) - })(_ => true) - conditionalizedPcs = conditionalizedPcs.map(replaceVars) + })() + conditionalizedPcs = conditionalizedPcs.map(replaceVars).filter { + case BuiltinEquals(p0, p1) => p0 != p1 + case _ => true + } val snapRhsUpdated = replaceVars(snapRhs) - - // Combine all variables that will be quantified over - var quantifiedVars = freshSnapRoot +: freshSnapshotMaps.filter(f => { + val newSnapshotMaps = freshSnapshotMaps.filter(f => { val id = f.func.id.name groups.exists({ case (from, to) => from == to && from == id }) - }).map(f => App(f.func, Seq.empty)).toSeq + }).map(f => App(f.func, Seq.empty)) + + def fromSnapTree(snap: Term, mwsfTerm: Term, pcsList: Vector[Term]): (Vector[Term], Vector[Term]) = { + snap match { + case Combine(snap1, snap2) => + val (pcsList1, terms1) = fromSnapTree(snap1, First(mwsfTerm), pcsList) + val (pcsList2, terms2) = fromSnapTree(snap2, Second(mwsfTerm), pcsList1) + (pcsList2, terms1 ++ terms2) + case _ => + var transformed = false + val pcsListNew = pcsList.map(pcs => { + snap match { + case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + transformed = true + pcs.transform { + case Lookup(field, fvf, at) if fvf == app => Lookup(field, SortWrapper(mwsfTerm, app.sort), at) + }() + case SortWrapper(Lookup(snapField, app: App, snapAt), _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + transformed = true + pcs.transform { + case l@Lookup(field, fvf, at) if fvf == app && field == snapField && at == snapAt => SortWrapper(mwsfTerm, l.sort) + }() + case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + transformed = true + pcs.transform { + case PredicateLookup(pred, psf, args) if psf == app => PredicateLookup(pred, SortWrapper(mwsfTerm, app.sort), args) + }() + case SortWrapper(PredicateLookup(snapPred, app: App, _), _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + transformed = true + pcs.transform { + case l@PredicateLookup(pred, psf, _) if psf == app && pred == snapPred => SortWrapper(mwsfTerm, l.sort) + }() + case _ => pcs + } + }) + (pcsListNew, if (transformed) Vector.empty else Vector(BuiltinEquals(mwsfTerm, snap))) + } + } + val (updatedPcs, mwsfTerms) = fromSnapTree(snapRhsUpdated, mwsfApply, conditionalizedPcs) + + // Combine all variables that will be quantified over + // var quantifiedVars = freshSnapRoot +: newSnapshotMaps.toSeq + val quantifiedVars = freshSnapRoot +: newSnapshotMaps.filter(app => updatedPcs.exists(pcs => pcs.contains(app))).toSeq // Partition path conditions into a set which include the freshSnapRoot and those which do not - var (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = conditionalizedPcs.partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) + var (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = updatedPcs.partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) // Remove forall quantifiers with the quantified variable in `quantifiedVars` pcsWithQuantifiedVars = pcsWithQuantifiedVars .map(_.transform { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.sort == sorts.Ref || v.sort == sorts.Snap => { - if (!quantifiedVars.contains(v)) quantifiedVars :+= v - body - } }(_ => true)) val quantifiers = quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)) - /* - * Rewrite based on test11 in QPFields.vpr - * --------------------------------------- - * Assume we have a MWSF and FVF with the following definition: - * ∀ t: Snap, freshSMs :: cond1 ==> mwsf.apply(t) == fvf - * and the following path condition that is part of cond1: - * ∀ r: Ref :: cond2 ==> fvf.lookup(r) == rhs - * Then we create the path condition: - * ∀ r: Ref, t: Snap, freshSMs :: cond1 ==> (cond2 ==> mwsf.apply(t).lookup(r) == rhs) - */ - def simplifyQuantification(snap: Term, to: Term): Vector[Term] = { - snap match { - case SortWrapper(app: App, _) if - app.applicable.resultSort.isInstanceOf[sorts.FieldValueFunction] || - app.applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction] => - - def rewriteTerms(r: Var, cond: Term, terms: Iterable[Term]): Vector[Term] = { - val newTerms = terms.collect { - case BuiltinEquals(Lookup(field, fvf, at), rhs) if r.sort == sorts.Ref && fvf == app && r == at => - val newLookup = Lookup(field, SortWrapper(mwsfApply, fvf.sort), at) - BuiltinEquals(newLookup, rhs) - case BuiltinEquals(PredicateLookup(predname, psf, args), rhs) if r.sort == sorts.Snap && psf == app && args == List(r) => - val newLookup = PredicateLookup(predname, SortWrapper(mwsfApply, psf.sort), args) - BuiltinEquals(newLookup, rhs) - } - if (newTerms.isEmpty) return Vector.empty - Vector(Forall( - quantifiers, - Implies(And(cond +: pcsWithQuantifiedVars), And(newTerms)), - newTerms.map { case BuiltinEquals(lhs, _) => Trigger(lhs) }.toSeq - )) - } - - conditionalizedPcs.flatMap { - case Quantification(Forall, Seq(r), Implies(cond, And(terms)), _, _, _, _) => rewriteTerms(r, cond, terms) - case Quantification(Forall, Seq(r), Implies(cond, term), _, _, _, _) => rewriteTerms(r, cond, Seq(term)) - case _ => Vector.empty - } - case Combine(snap1, snap2) => simplifyQuantification(snap1, First(to)) ++ simplifyQuantification(snap2, Second(to)) - case _ => Vector.empty - } - } - val pcsUpdated = simplifyQuantification(snapRhsUpdated, mwsfApply) - - /* - * Rewrite based on test9 in QPFields.vpr - * --------------------------------------- - * Assume we have a mwsf, a fvf, and some reference ref with following definition: - * ∀ t: Snap :: mwsf.apply(t) == fvf.lookup(ref) - * and the path condition: - * cond ==> fvf.lookup(ref) == rhs - * then we can add the path condition to: - * ∀ t: Snap :: cond ==> mwsf.apply(t) == rhs - */ - // case SortWrapper(lookup, to) if - // (lookup.isInstanceOf[Lookup] && - // lookup.asInstanceOf[Lookup].fvf.isInstanceOf[App] && - // lookup.asInstanceOf[Lookup].fvf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.FieldValueFunction]) || - // (lookup.isInstanceOf[PredicateLookup] && - // lookup.asInstanceOf[PredicateLookup].psf.isInstanceOf[App] && - // lookup.asInstanceOf[PredicateLookup].psf.asInstanceOf[App].applicable.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) => - // - // val app = lookup match { - // case Lookup(_, fvf, _) => fvf - // case PredicateLookup(_, psf, _) => psf - // } - // - // def rewriteTerms(cond: Term, terms: Iterable[Term]): Vector[Quantification] = { - // val newTerms = terms.collect { - // case BuiltinEquals(Lookup(_, fvf, at), rhs) - // if fvf == app && rhs.contains(freshSnapRoot) && lookup.isInstanceOf[Lookup] && lookup.asInstanceOf[Lookup].at == at => - // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) - // case BuiltinEquals(PredicateLookup(_, psf, args), rhs) - // if psf == app && rhs.contains(freshSnapRoot) /* && lookup.isInstanceOf[PredicateLookup] && lookup.asInstanceOf[PredicateLookup].args == args */ => - // BuiltinEquals(mwsfApply, SortWrapper(rhs, to)) - // } - // if (newTerms.isEmpty) return Vector.empty - // Vector(Forall(freshSnapRoot, Implies(cond, And(newTerms)), Trigger(mwsfApply))) - // } - // - // conditionalizedPcs.flatMap { - // case Implies(cond, And(terms)) => rewriteTerms(cond, terms) - // case Implies(cond, eq: BuiltinEquals) => rewriteTerms(cond, Seq(eq)) - // case _ => Vector.empty - // } - // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions val pcsQuantified = Forall( quantifiers, Implies( - And(pcsWithQuantifiedVars), - BuiltinEquals(mwsfApply, snapRhsUpdated) + App(imgFun, Seq(freshSnapRoot)), + And(mwsfTerms ++ pcsWithQuantifiedVars) ), Trigger(mwsfApply) ) v1.decider.assumeDefinition(pcsQuantified) // Return the summarized path conditions - pcsWithoutQuantifiedVars ++ pcsUpdated :+ pcsQuantified + pcsWithoutQuantifiedVars :+ pcsQuantified } /** @@ -413,6 +367,7 @@ object magicWandSupporter extends SymbolicExecutionRules { freshSnapRoot: Var, mwsf: Var, snapRhs: Term, + imgFun: Function, functionsBeforePackaging: Set[FunctionDecl], v5: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") @@ -421,7 +376,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) val conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs.tail match { case empty@Seq() => empty @@ -451,7 +406,8 @@ object magicWandSupporter extends SymbolicExecutionRules { val preMark = v4.decider.setPathConditionMark() v4.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val mwsf = v4.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val imgFun = v4.decider.fresh("img", Seq(sorts.Snap), sorts.Bool) // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { @@ -463,12 +419,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v5.decider.prover.comment("Definitional axioms for singleton-SM's value") v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) }) } else { - val wandSnapshot = MagicWandSnapshot(mwsf) + val wandSnapshot = MagicWandSnapshot((mwsf, imgFun)) this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) }) } } @@ -595,7 +551,9 @@ object magicWandSupporter extends SymbolicExecutionRules { // Convert snapWand to MWSF val mwsf = snapWand match { - case SortWrapper(mwsf: MagicWandSnapshot, _) => mwsf + case SortWrapper(mwsf: MagicWandSnapshot, _) => + v.decider.assume(App(mwsf.imgFun, Seq(snapLhs))) + mwsf case SortWrapper(snapshot, _) => SortWrapper(snapshot, sorts.MagicWandSnapFunction) case _ => SortWrapper(snapWand, sorts.MagicWandSnapFunction) } diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index f3976c827..75e36f645 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -398,7 +398,14 @@ object producer extends ProductionRules { case wand: ast.MagicWand => val snap = sf(sorts.MagicWandSnapFunction, v) - magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => + val imgFun = v.decider.fresh("img", Seq(sorts.Snap), sorts.Bool) + snap.transform { + case mws@MagicWandSnapshot(_, img) => + val q = Var(Identifier("snap"), sorts.Snap, false) + v.decider.assumeDefinition(Forall(q, Implies(App(imgFun, q), App(img, q)), Trigger(App(imgFun, q)))) + mws + }(_ => true) + magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap, imgFun), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 9b0d7516a..9334cd579 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2307,20 +2307,20 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * In the symbolic execution when we apply a magic wand, it consumes the left-hand side * and uses this function and the resulting snapshot to look up which right-hand side to produce. */ -class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { +class MagicWandSnapshot(val mwsf: Term, val imgFun: Function) extends Term with ConditionalFlyweight[(Term, Function), MagicWandSnapshot] { utils.assertSort(mwsf, "magic wand snap function", sorts.MagicWandSnapFunction) override val sort: Sort = sorts.MagicWandSnapFunction - override lazy val toString = s"wandSnap($mwsf)" + override lazy val toString = s"wandSnap($mwsf, $imgFun)" - override val equalityDefiningMembers: Term = mwsf + override val equalityDefiningMembers: (Term, Function) = (mwsf, imgFun) } -object MagicWandSnapshot extends PreciseCondFlyweightFactory[Term, MagicWandSnapshot] { +object MagicWandSnapshot extends PreciseCondFlyweightFactory[(Term, Function), MagicWandSnapshot] { /** Create an instance of [[viper.silicon.state.terms.MagicWandSnapshot]]. */ - override def actualCreate(arg: Term): MagicWandSnapshot = - new MagicWandSnapshot(arg) + override def actualCreate(arg: (Term, Function)): MagicWandSnapshot = + new MagicWandSnapshot(arg._1, arg._2) } /** diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index 80e9d32d0..9f89b4881 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -91,7 +91,7 @@ package object utils { case PredicatePermLookup(_, pm, args) => Seq(pm) ++ args case FieldTrigger(_, fvf, at) => fvf :: at :: Nil case PredicateTrigger(_, psf, args) => psf +: args - case MagicWandSnapshot(mwsf) => mwsf :: Nil + case MagicWandSnapshot(mwsf, _) => mwsf :: Nil } /** @see [[viper.silver.ast.utility.Simplifier.simplify]] */ @@ -196,7 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) + case MagicWandSnapshot(t0, t1) => MagicWandSnapshot((go(t0), t1)) case MWSFApply(t0, t1) => MWSFApply(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) From d3963892ed1bccb73d72ca7388e921d025e7b157 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:26:05 +0200 Subject: [PATCH 37/43] Replace image function with inverse function. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 79 ++++++++++--------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/silver b/silver index c29583793..7eaf86ac3 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit c295837930686bea707174164bcb2cd0a8689a67 +Subproject commit 7eaf86ac3e8e4b778c01fde55e571c820860f8ef diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 6ca5fa214..82d69f3e2 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -184,25 +184,25 @@ object magicWandSupporter extends SymbolicExecutionRules { } /** - * Partition path conditions into a set which include the freshSnapRoot and those which do not. - * Include the path conditions with the freshSnapRoot in the MWSF definition. + * Partition path conditions into a set which include the abstractLhs and those which do not. + * Include the path conditions with the abstractLhs in the MWSF definition. * It also takes care of special cases that include field value functions. * - * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. - * @param freshSnapRoot Fresh variable that represents the snapshot of the wand's LHS. - * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. - * @param snapRhs Snapshot of the wand's RHS. - * @param v1 Verifier instance. + * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. + * @param abstractLhs Fresh variable that represents the snapshot of the wand's LHS. + * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. + * @param snapRhs Snapshot of the wand's RHS. + * @param v1 Verifier instance. * @return Vector of conserved path conditions. */ private def summarizeDefinitions(conservedPcs: Vector[RecordedPathConditions], - freshSnapRoot: Var, + abstractLhs: Var, mwsf: Var, snapRhs: Term, - imgFun: Function, + invFun: Function, functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { - val mwsfApply = MWSFApply(mwsf, freshSnapRoot) + val mwsfApply = MWSFApply(mwsf, abstractLhs) val freshSnapshotMaps = (v1.decider.freshFunctions -- functionsBeforePackaging) .filter(f => f.func.resultSort.isInstanceOf[sorts.FieldValueFunction] || f.func.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) @@ -301,26 +301,26 @@ object magicWandSupporter extends SymbolicExecutionRules { val (updatedPcs, mwsfTerms) = fromSnapTree(snapRhsUpdated, mwsfApply, conditionalizedPcs) // Combine all variables that will be quantified over - // var quantifiedVars = freshSnapRoot +: newSnapshotMaps.toSeq - val quantifiedVars = freshSnapRoot +: newSnapshotMaps.filter(app => updatedPcs.exists(pcs => pcs.contains(app))).toSeq + val quantifiedVars = abstractLhs +: newSnapshotMaps.filter(app => updatedPcs.exists(pcs => pcs.contains(app))).toSeq - // Partition path conditions into a set which include the freshSnapRoot and those which do not + // Partition path conditions into a set which include the abstractLhs and those which do not var (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = updatedPcs.partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) // Remove forall quantifiers with the quantified variable in `quantifiedVars` pcsWithQuantifiedVars = pcsWithQuantifiedVars .map(_.transform { - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(freshSnapRoot) => body - }(_ => true)) - val quantifiers = quantifiedVars.map(t => Var(t.applicable.id, t.applicable.resultSort, false)) + case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(abstractLhs) => body + }(_ => true)) ++ mwsfTerms - // Combine all path conditions which include the freshSnapRoot and add it to the verifier's list of definitions + // Add a definition for the inverse function only if it is used in the path conditions + if (pcsWithQuantifiedVars.exists(pcs => pcs.existsDefined { case App(f, _) if f == invFun =>})) { + pcsWithQuantifiedVars :+= BuiltinEquals(App(invFun, Seq(mwsfApply)), abstractLhs) + } + + // Combine all path conditions which include the abstractLhs and add it to the verifier's list of definitions val pcsQuantified = Forall( - quantifiers, - Implies( - App(imgFun, Seq(freshSnapRoot)), - And(mwsfTerms ++ pcsWithQuantifiedVars) - ), + abstractLhs, + And(pcsWithQuantifiedVars), Trigger(mwsfApply) ) v1.decider.assumeDefinition(pcsQuantified) @@ -364,10 +364,10 @@ object magicWandSupporter extends SymbolicExecutionRules { def appendToRecordedBranches(s5: State, ch: Chunk, pcs: RecordedPathConditions, - freshSnapRoot: Var, + abstractLhs: Var, mwsf: Var, snapRhs: Term, - imgFun: Function, + invFun: Function, functionsBeforePackaging: Set[FunctionDecl], v5: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") @@ -376,7 +376,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) val conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs.tail match { case empty@Seq() => empty @@ -398,15 +398,15 @@ object magicWandSupporter extends SymbolicExecutionRules { } def createWandChunkAndRecordResults(s4: State, - freshSnapRoot: Var, + abstractLhs: Var, snapRhs: Term, functionsBeforePackaging: Set[FunctionDecl], + mwsf: Var, + invFun: Function, v4: Verifier) : VerificationResult = { val preMark = v4.decider.setPathConditionMark() - v4.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val mwsf = v4.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val imgFun = v4.decider.fresh("img", Seq(sorts.Snap), sorts.Bool) // If the wand is used as a quantified resource anywhere in the program @@ -419,12 +419,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v5.decider.prover.comment("Definitional axioms for singleton-SM's value") v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) }) } else { val wandSnapshot = MagicWandSnapshot((mwsf, imgFun)) this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), freshSnapRoot, mwsf, snapRhs, imgFun, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) }) } } @@ -440,18 +440,23 @@ object magicWandSupporter extends SymbolicExecutionRules { recordPcs = true, parallelizeBranches = false) + v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + val invFun = v.decider.fresh("inv", Seq(sorts.Snap), sorts.Snap) + val tempResult = executionFlowController.locally(sEmp, v)((s1, v1) => { /* A snapshot (binary tree) will be constructed using First/Second datatypes, * that preserves the original root. The leafs of this tree will later appear * in the snapshot of the RHS at the appropriate places. Thus equating - * `freshSnapRoot` with the snapshot received from consuming the LHS when + * `abstractLhs` with the snapshot received from consuming the LHS when * applying the wand preserves values from the LHS into the RHS. */ - val freshSnapRoot = freshSnap(sorts.Snap, v1) + val abstractLhs = freshSnap(sorts.Snap, v1) + val invSnap = App(invFun, Seq(MWSFApply(mwsf, abstractLhs))) val functionsBeforePackaging = v1.decider.freshFunctions // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(freshSnapRoot), wand.left, pve, v1)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(invSnap), wand.left, pve, v1)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either @@ -486,7 +491,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. val s3 = sProofScript.copy(oldHeaps = s2.oldHeaps, reserveCfgs = sProofScript.reserveCfgs.tail) consume(s3, wand.right, pve, v3)((s4, snapRhs, v4) => - createWandChunkAndRecordResults(s4, freshSnapRoot, snapRhs, functionsBeforePackaging, v4) + createWandChunkAndRecordResults(s4, abstractLhs, snapRhs, functionsBeforePackaging, mwsf, invFun, v4) ) }) }) @@ -497,7 +502,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v.decider.freshFunctions, v) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v.decider.freshFunctions, mwsf, invFun, v) } recordedBranches.foldLeft(tempResult)((prevRes, recordedBranch) => { @@ -551,9 +556,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // Convert snapWand to MWSF val mwsf = snapWand match { - case SortWrapper(mwsf: MagicWandSnapshot, _) => - v.decider.assume(App(mwsf.imgFun, Seq(snapLhs))) - mwsf + case SortWrapper(snapshot: MagicWandSnapshot, _) => snapshot.mwsf case SortWrapper(snapshot, _) => SortWrapper(snapshot, sorts.MagicWandSnapFunction) case _ => SortWrapper(snapWand, sorts.MagicWandSnapFunction) } From 2c722048d4c10a3c3510414224ea8ffbc25c270e Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:32:10 +0200 Subject: [PATCH 38/43] Update silver. --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 7eaf86ac3..709b7630d 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 7eaf86ac3e8e4b778c01fde55e571c820860f8ef +Subproject commit 709b7630dbb2aa505573573a45fc554acbd276a2 From 208e50c3245337c7197c578da64e8ac7037c0dd0 Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:36:10 +0200 Subject: [PATCH 39/43] Reverse adding field imgFun to MagicWandSnapshot. --- src/main/scala/decider/TermToSMTLib2Converter.scala | 2 +- src/main/scala/rules/HavocSupporter.scala | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 4 +--- src/main/scala/rules/Producer.scala | 9 +-------- src/main/scala/state/Terms.scala | 12 ++++++------ src/main/scala/state/Utils.scala | 4 ++-- 6 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/main/scala/decider/TermToSMTLib2Converter.scala b/src/main/scala/decider/TermToSMTLib2Converter.scala index b511f7d7e..6d278a8dd 100644 --- a/src/main/scala/decider/TermToSMTLib2Converter.scala +++ b/src/main/scala/decider/TermToSMTLib2Converter.scala @@ -315,7 +315,7 @@ class TermToSMTLib2Converter val docBindings = ssep((bindings.toSeq map (p => parens(render(p._1) <+> render(p._2)))).to(collection.immutable.Seq), space) parens(text("let") <+> parens(docBindings) <+> render(body)) - case MagicWandSnapshot(mwsf, _) => render(mwsf) + case MagicWandSnapshot(mwsf) => render(mwsf) case MWSFApply(mwsf, snap) => renderApp("MWSF_apply", Seq(mwsf, snap), sorts.Snap) case _: MagicWandChunkTerm diff --git a/src/main/scala/rules/HavocSupporter.scala b/src/main/scala/rules/HavocSupporter.scala index ec5adefd4..d146c72ee 100644 --- a/src/main/scala/rules/HavocSupporter.scala +++ b/src/main/scala/rules/HavocSupporter.scala @@ -185,7 +185,7 @@ object havocSupporter extends SymbolicExecutionRules { case ch: MagicWandChunk => val havockedSnap = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) val cond = replacementCond(lhs, ch.args, condInfo) - val magicWandSnapshot = MagicWandSnapshot((Ite(cond, havockedSnap, ch.snap.mwsf), ch.snap.imgFun)) + val magicWandSnapshot = MagicWandSnapshot(Ite(cond, havockedSnap, ch.snap.mwsf)) ch.withSnap(magicWandSnapshot) case ch => diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index 82d69f3e2..fb64cf46e 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -407,8 +407,6 @@ object magicWandSupporter extends SymbolicExecutionRules { : VerificationResult = { val preMark = v4.decider.setPathConditionMark() - val imgFun = v4.decider.fresh("img", Seq(sorts.Snap), sorts.Bool) - // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { val bodyVars = wand.subexpressionsToEvaluate(s.program) @@ -422,7 +420,7 @@ object magicWandSupporter extends SymbolicExecutionRules { appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) }) } else { - val wandSnapshot = MagicWandSnapshot((mwsf, imgFun)) + val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) }) diff --git a/src/main/scala/rules/Producer.scala b/src/main/scala/rules/Producer.scala index 75e36f645..f3976c827 100644 --- a/src/main/scala/rules/Producer.scala +++ b/src/main/scala/rules/Producer.scala @@ -398,14 +398,7 @@ object producer extends ProductionRules { case wand: ast.MagicWand => val snap = sf(sorts.MagicWandSnapFunction, v) - val imgFun = v.decider.fresh("img", Seq(sorts.Snap), sorts.Bool) - snap.transform { - case mws@MagicWandSnapshot(_, img) => - val q = Var(Identifier("snap"), sorts.Snap, false) - v.decider.assumeDefinition(Forall(q, Implies(App(imgFun, q), App(img, q)), Trigger(App(imgFun, q)))) - mws - }(_ => true) - magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap, imgFun), pve, v)((s1, chWand, v1) => + magicWandSupporter.createChunk(s, wand, MagicWandSnapshot(snap), pve, v)((s1, chWand, v1) => chunkSupporter.produce(s1, s1.h, chWand, v1)((s2, h2, v2) => Q(s2.copy(h = h2), v2))) diff --git a/src/main/scala/state/Terms.scala b/src/main/scala/state/Terms.scala index 9334cd579..9b0d7516a 100644 --- a/src/main/scala/state/Terms.scala +++ b/src/main/scala/state/Terms.scala @@ -2307,20 +2307,20 @@ object PredicateTrigger extends PreciseCondFlyweightFactory[(String, Term, Seq[T * In the symbolic execution when we apply a magic wand, it consumes the left-hand side * and uses this function and the resulting snapshot to look up which right-hand side to produce. */ -class MagicWandSnapshot(val mwsf: Term, val imgFun: Function) extends Term with ConditionalFlyweight[(Term, Function), MagicWandSnapshot] { +class MagicWandSnapshot(val mwsf: Term) extends Term with ConditionalFlyweight[Term, MagicWandSnapshot] { utils.assertSort(mwsf, "magic wand snap function", sorts.MagicWandSnapFunction) override val sort: Sort = sorts.MagicWandSnapFunction - override lazy val toString = s"wandSnap($mwsf, $imgFun)" + override lazy val toString = s"wandSnap($mwsf)" - override val equalityDefiningMembers: (Term, Function) = (mwsf, imgFun) + override val equalityDefiningMembers: Term = mwsf } -object MagicWandSnapshot extends PreciseCondFlyweightFactory[(Term, Function), MagicWandSnapshot] { +object MagicWandSnapshot extends PreciseCondFlyweightFactory[Term, MagicWandSnapshot] { /** Create an instance of [[viper.silicon.state.terms.MagicWandSnapshot]]. */ - override def actualCreate(arg: (Term, Function)): MagicWandSnapshot = - new MagicWandSnapshot(arg._1, arg._2) + override def actualCreate(arg: Term): MagicWandSnapshot = + new MagicWandSnapshot(arg) } /** diff --git a/src/main/scala/state/Utils.scala b/src/main/scala/state/Utils.scala index 9f89b4881..80e9d32d0 100644 --- a/src/main/scala/state/Utils.scala +++ b/src/main/scala/state/Utils.scala @@ -91,7 +91,7 @@ package object utils { case PredicatePermLookup(_, pm, args) => Seq(pm) ++ args case FieldTrigger(_, fvf, at) => fvf :: at :: Nil case PredicateTrigger(_, psf, args) => psf +: args - case MagicWandSnapshot(mwsf, _) => mwsf :: Nil + case MagicWandSnapshot(mwsf) => mwsf :: Nil } /** @see [[viper.silver.ast.utility.Simplifier.simplify]] */ @@ -196,7 +196,7 @@ package object utils { case MapUpdate(t0, t1, t2) => MapUpdate(go(t0), go(t1), go(t2)) case MapDomain(t) => MapDomain(go(t)) case MapRange(t) => MapRange(go(t)) - case MagicWandSnapshot(t0, t1) => MagicWandSnapshot((go(t0), t1)) + case MagicWandSnapshot(t) => MagicWandSnapshot(go(t)) case MWSFApply(t0, t1) => MWSFApply(go(t0), go(t1)) case Combine(t0, t1) => Combine(go(t0), go(t1)) case First(t) => First(go(t)) From 90a42f97d776cf4cfbe800104aa9038851b4c8fc Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:40:19 +0200 Subject: [PATCH 40/43] Remove inverse functions. --- src/main/scala/rules/MagicWandSupporter.scala | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index fb64cf46e..b09f8a3d9 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -199,7 +199,6 @@ object magicWandSupporter extends SymbolicExecutionRules { abstractLhs: Var, mwsf: Var, snapRhs: Term, - invFun: Function, functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { val mwsfApply = MWSFApply(mwsf, abstractLhs) @@ -312,11 +311,6 @@ object magicWandSupporter extends SymbolicExecutionRules { case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(abstractLhs) => body }(_ => true)) ++ mwsfTerms - // Add a definition for the inverse function only if it is used in the path conditions - if (pcsWithQuantifiedVars.exists(pcs => pcs.existsDefined { case App(f, _) if f == invFun =>})) { - pcsWithQuantifiedVars :+= BuiltinEquals(App(invFun, Seq(mwsfApply)), abstractLhs) - } - // Combine all path conditions which include the abstractLhs and add it to the verifier's list of definitions val pcsQuantified = Forall( abstractLhs, @@ -367,7 +361,6 @@ object magicWandSupporter extends SymbolicExecutionRules { abstractLhs: Var, mwsf: Var, snapRhs: Term, - invFun: Function, functionsBeforePackaging: Set[FunctionDecl], v5: Verifier): VerificationResult = { assert(s5.conservedPcs.nonEmpty, s"Unexpected structure of s5.conservedPcs: ${s5.conservedPcs}") @@ -376,7 +369,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // new permission and snapshot maps, which are in general necessary to proceed after the // package statement, e.g. to know which permissions have been consumed. // Here, we want to keep *only* the definitions, but no other path conditions. - val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) + val conservedPcs: Vector[Term] = summarizeDefinitions(s5.conservedPcs.head :+ pcs.definitionsOnly, abstractLhs, mwsf, snapRhs, functionsBeforePackaging, v5) val conservedPcsStack: Stack[Vector[RecordedPathConditions]] = s5.conservedPcs.tail match { case empty@Seq() => empty @@ -401,12 +394,13 @@ object magicWandSupporter extends SymbolicExecutionRules { abstractLhs: Var, snapRhs: Term, functionsBeforePackaging: Set[FunctionDecl], - mwsf: Var, - invFun: Function, v4: Verifier) : VerificationResult = { val preMark = v4.decider.setPathConditionMark() + v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") + val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) + // If the wand is used as a quantified resource anywhere in the program if (s4.qpMagicWands.contains(MagicWandIdentifier(wand, s.program))) { val bodyVars = wand.subexpressionsToEvaluate(s.program) @@ -417,12 +411,12 @@ object magicWandSupporter extends SymbolicExecutionRules { v5.decider.prover.comment("Definitional axioms for singleton-SM's value") v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, functionsBeforePackaging, v5) }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) this.createChunk(s4, wand, wandSnapshot, pve, v4)((s5, ch, v5) => { - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, invFun, functionsBeforePackaging, v5) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, functionsBeforePackaging, v5) }) } } @@ -438,10 +432,6 @@ object magicWandSupporter extends SymbolicExecutionRules { recordPcs = true, parallelizeBranches = false) - v.decider.prover.comment(s"Create MagicWandSnapFunction for wand $wand") - val mwsf = v.decider.fresh("mwsf", sorts.MagicWandSnapFunction) - val invFun = v.decider.fresh("inv", Seq(sorts.Snap), sorts.Snap) - val tempResult = executionFlowController.locally(sEmp, v)((s1, v1) => { /* A snapshot (binary tree) will be constructed using First/Second datatypes, * that preserves the original root. The leafs of this tree will later appear @@ -450,11 +440,10 @@ object magicWandSupporter extends SymbolicExecutionRules { * applying the wand preserves values from the LHS into the RHS. */ val abstractLhs = freshSnap(sorts.Snap, v1) - val invSnap = App(invFun, Seq(MWSFApply(mwsf, abstractLhs))) val functionsBeforePackaging = v1.decider.freshFunctions // Produce the wand's LHS. - produce(s1.copy(conservingSnapshotGeneration = true), toSf(invSnap), wand.left, pve, v1)((sLhs, v2) => { + produce(s1.copy(conservingSnapshotGeneration = true), toSf(abstractLhs), wand.left, pve, v1)((sLhs, v2) => { val proofScriptCfg = proofScript.toCfg() /* Expected shape of reserveHeaps is either @@ -489,7 +478,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // This part indirectly calls the methods `this.transfer` and `this.consumeFromMultipleHeaps`. val s3 = sProofScript.copy(oldHeaps = s2.oldHeaps, reserveCfgs = sProofScript.reserveCfgs.tail) consume(s3, wand.right, pve, v3)((s4, snapRhs, v4) => - createWandChunkAndRecordResults(s4, abstractLhs, snapRhs, functionsBeforePackaging, mwsf, invFun, v4) + createWandChunkAndRecordResults(s4, abstractLhs, snapRhs, functionsBeforePackaging, v4) ) }) }) @@ -500,7 +489,7 @@ object magicWandSupporter extends SymbolicExecutionRules { // and thus, that no wand chunk was created. In order to continue, we create one now. // Moreover, we need to set reserveHeaps to structurally match [State RHS] below. val s1 = sEmp.copy(reserveHeaps = Heap() +: Heap() +: Heap() +: s.reserveHeaps.tail) - createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v.decider.freshFunctions, mwsf, invFun, v) + createWandChunkAndRecordResults(s1, freshSnap(sorts.Snap, v), freshSnap(sorts.Snap, v), v.decider.freshFunctions, v) } recordedBranches.foldLeft(tempResult)((prevRes, recordedBranch) => { From 2865e9475736f7c202be7bc603d2ce5a41b8d55c Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:03:37 +0200 Subject: [PATCH 41/43] Rename test files. --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 709b7630d..547cbdd1e 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 709b7630dbb2aa505573573a45fc554acbd276a2 +Subproject commit 547cbdd1eee86eca410a2e7a1492c4b02aa82c7a From 8ac2fb89fe0ff259badccc0028ca99ea2eb5e99a Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:00:30 +0200 Subject: [PATCH 42/43] Fix quantified magic wands and update documentation of `summarizeDefinitions`. --- silver | 2 +- src/main/scala/rules/MagicWandSupporter.scala | 164 ++++++++++-------- 2 files changed, 91 insertions(+), 75 deletions(-) diff --git a/silver b/silver index 547cbdd1e..2f8d64b4e 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 547cbdd1eee86eca410a2e7a1492c4b02aa82c7a +Subproject commit 2f8d64b4e2e0d51b135f0d5a82a116be1bfb2095 diff --git a/src/main/scala/rules/MagicWandSupporter.scala b/src/main/scala/rules/MagicWandSupporter.scala index b09f8a3d9..102f489df 100644 --- a/src/main/scala/rules/MagicWandSupporter.scala +++ b/src/main/scala/rules/MagicWandSupporter.scala @@ -21,6 +21,9 @@ import viper.silicon.utils.ast.BigAnd import viper.silicon.utils.{freshSnap, toSf} import viper.silicon.verifier.Verifier +import scala.annotation.tailrec +import scala.collection.immutable.Map + object magicWandSupporter extends SymbolicExecutionRules { import consumer._ import evaluator._ @@ -184,34 +187,45 @@ object magicWandSupporter extends SymbolicExecutionRules { } /** - * Partition path conditions into a set which include the abstractLhs and those which do not. - * Include the path conditions with the abstractLhs in the MWSF definition. - * It also takes care of special cases that include field value functions. + * Summarize all path conditions and include them in the definition of the MWSF. + * + * This method looks for all snapshot maps that were introduced during the packaging of the wand. It then tries + * to find a single representative snapshot map for each group of equal snapshot maps. + * + * Then it breaks up `snapRhs` into a set of equalities such that we can replace all occurrences of a snapshot map + * by a term that involves the MWSF. The goal is to replace all occurrences of snapshot maps which were introduced + * during the packaging of the wand and therefore are only temporary objects that won't be used after the packaging. * - * @param conservedPcs Vector of path conditions which have been recorded during the execution of the proof script. - * @param abstractLhs Fresh variable that represents the snapshot of the wand's LHS. - * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. - * @param snapRhs Snapshot of the wand's RHS. - * @param v1 Verifier instance. - * @return Vector of conserved path conditions. + * @param recordedPathConditions Vector of path conditions which have been recorded during the execution of the proof script. + * @param abstractLhs Fresh variable that represents the snapshot of the wand's LHS. + * @param mwsf MagicWandSnapFunction that is used to lookup the snapshot of the wand's RHS. + * @param snapRhs Snapshot of the wand's RHS. + * @param functionsBeforePackaging Set of functions that were present before the packaging of the wand. + * @param v1 Verifier instance. + * @return Set of all path conditions including the MWSF definition. */ - private def summarizeDefinitions(conservedPcs: Vector[RecordedPathConditions], + private def summarizeDefinitions(recordedPathConditions: Vector[RecordedPathConditions], abstractLhs: Var, - mwsf: Var, + mwsf: Term, snapRhs: Term, functionsBeforePackaging: Set[FunctionDecl], v1: Verifier): Vector[Term] = { val mwsfApply = MWSFApply(mwsf, abstractLhs) + + // Find all snapshot maps that were introduced during the packaging of the wand val freshSnapshotMaps = (v1.decider.freshFunctions -- functionsBeforePackaging) .filter(f => f.func.resultSort.isInstanceOf[sorts.FieldValueFunction] || f.func.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) // Map all path conditions to their conditionalized form and flatten the result - var conditionalizedPcs = conservedPcs.flatMap(_.conditionalized).flatMap { + var conditionalizedPcs = recordedPathConditions.flatMap(_.conditionalized).flatMap { case And(terms) => terms case term => Vector(term) } - // Identify whether two or three of the snapshot maps are equal + // Helper function to find the parent node of a snapshot map in a union-find data structure + @tailrec def findParentNode(map: Map[String, String], x: String): String = if (map(x) == x) x else findParentNode(map, map(x)) + + // Identify groups of equal snapshot maps val groups = conditionalizedPcs.collect { // Extract names of all FVFs and PSFs that are part of an equality of the form: fvf0 == fvf1 or psf0 == psf1 case BuiltinEquals(App(fvf0, Seq()), App(fvf1, Seq())) @@ -223,104 +237,105 @@ object magicWandSupporter extends SymbolicExecutionRules { }.foldLeft( freshSnapshotMaps.map(f => (f.func.id.name, f.func.id.name)).toMap )((assignments, eq) => { - // Find all groups of equal snapshot maps + // Find all groups of equal snapshot maps using a union-find data structure val (a, b) = eq if (!assignments.contains(a) || !assignments.contains(b)) { assignments } else { - val assA = assignments(a) - val assB = assignments(b) + val assA = findParentNode(assignments, a) + val assB = findParentNode(assignments, b) if (assA == assB) { assignments } else if (assA < assB) { - assignments.map { - case (x, y) => if (y == assB) (x, assA) else (x, y) - } + assignments.updated(assB, assA) } else { - assignments.map { - case (x, y) => if (y == assA) (x, assB) else (x, y) - } + assignments.updated(assA, assB) } } }) - // Replace all snapshot maps with one snapshot map of the group they belong to - def replaceVars(t: Term): Term = t.transform({ + // Replace all snapshot maps with the one snapshot map of the group they belong to + def replaceSnapshotMaps(t: Term): Term = t.transform({ case App(fun: Fun, Seq()) if (fun.resultSort.isInstanceOf[sorts.FieldValueFunction] || fun.resultSort.isInstanceOf[sorts.PredicateSnapFunction]) && groups.contains(fun.id.name) => - App(fun.copy(id = Identifier(groups(fun.id.name))), Seq()) + App(fun.copy(id = Identifier(findParentNode(groups, fun.id.name))), Seq()) })() - conditionalizedPcs = conditionalizedPcs.map(replaceVars).filter { + val snapRhsUpdated = replaceSnapshotMaps(snapRhs) + conditionalizedPcs = conditionalizedPcs.map(replaceSnapshotMaps) + + // If there is a term `Lookup(_, sm, _) == abstractLhs`, replace all occurrences of `Lookup(_, sm, _)` with `abstractLhs` + conditionalizedPcs.find({ + case BuiltinEquals(Lookup(_, fvf, _), SortWrapper(`abstractLhs`, _)) => fvf.sort.isInstanceOf[sorts.FieldValueFunction] + case BuiltinEquals(PredicateLookup(_, psf, _), SortWrapper(`abstractLhs`, _)) => psf.sort.isInstanceOf[sorts.PredicateSnapFunction] + case _ => false + }) match { + case Some(BuiltinEquals(Lookup(_, fvf, _), _)) => + conditionalizedPcs = conditionalizedPcs.map(_.transform { + case l@Lookup(_, `fvf`, _) if fvf.sort.isInstanceOf[sorts.FieldValueFunction] => SortWrapper(abstractLhs, l.sort) + }()) + case Some(BuiltinEquals(PredicateLookup(_, psf, _), _)) => + conditionalizedPcs = conditionalizedPcs.map(_.transform { + case l@PredicateLookup(_, `psf`, _) if psf.sort.isInstanceOf[sorts.PredicateSnapFunction] => SortWrapper(abstractLhs, l.sort) + }()) + case _ => + } + + // Remove all path conditions of the form `p0 == p1` where p0 is syntactically equal to p1 + conditionalizedPcs = conditionalizedPcs.filter { case BuiltinEquals(p0, p1) => p0 != p1 case _ => true } - val snapRhsUpdated = replaceVars(snapRhs) + + // Transform the term `mwsfLookup == snapRhs` into a set of equalities using First and Second constructors on `snapRhs` + // If the resulting term is a snapshot map or a lookup of a snapshot map, substitute it with a corresponding MWSF term val newSnapshotMaps = freshSnapshotMaps.filter(f => { val id = f.func.id.name groups.exists({ case (from, to) => from == to && from == id }) }).map(f => App(f.func, Seq.empty)) - - def fromSnapTree(snap: Term, mwsfTerm: Term, pcsList: Vector[Term]): (Vector[Term], Vector[Term]) = { + def substituteMwsfTerm(snap: Term, mwsfTerm: Term, pcsList: Vector[Term]): (Vector[Term], Vector[Term]) = { snap match { case Combine(snap1, snap2) => - val (pcsList1, terms1) = fromSnapTree(snap1, First(mwsfTerm), pcsList) - val (pcsList2, terms2) = fromSnapTree(snap2, Second(mwsfTerm), pcsList1) + val (pcsList1, terms1) = substituteMwsfTerm(snap1, First(mwsfTerm), pcsList) + val (pcsList2, terms2) = substituteMwsfTerm(snap2, Second(mwsfTerm), pcsList1) (pcsList2, terms1 ++ terms2) case _ => var transformed = false - val pcsListNew = pcsList.map(pcs => { - snap match { - case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + val pcsListNew = snap match { + case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + pcsList.map(pcs => { transformed = true - pcs.transform { - case Lookup(field, fvf, at) if fvf == app => Lookup(field, SortWrapper(mwsfTerm, app.sort), at) - }() - case SortWrapper(Lookup(snapField, app: App, snapAt), _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + pcs.transform { case Lookup(field, `app`, at) => Lookup(field, SortWrapper(mwsfTerm, app.sort), at) }() + }) + case SortWrapper(Lookup(snapField, app: App, snapAt), _) if app.sort.isInstanceOf[sorts.FieldValueFunction] && newSnapshotMaps.contains(app) => + pcsList.map(pcs => { transformed = true - pcs.transform { - case l@Lookup(field, fvf, at) if fvf == app && field == snapField && at == snapAt => SortWrapper(mwsfTerm, l.sort) - }() - case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + pcs.transform { case l@Lookup(`snapField`, `app`, `snapAt`) => SortWrapper(mwsfTerm, l.sort) }() + }) + case SortWrapper(app: App, _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + pcsList.map(pcs => { transformed = true - pcs.transform { - case PredicateLookup(pred, psf, args) if psf == app => PredicateLookup(pred, SortWrapper(mwsfTerm, app.sort), args) - }() - case SortWrapper(PredicateLookup(snapPred, app: App, _), _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + pcs.transform { case PredicateLookup(pred, `app`, args) => PredicateLookup(pred, SortWrapper(mwsfTerm, app.sort), args) }() + }) + case SortWrapper(PredicateLookup(snapPred, app: App, _), _) if app.sort.isInstanceOf[sorts.PredicateSnapFunction] && newSnapshotMaps.contains(app) => + pcsList.map(pcs => { transformed = true - pcs.transform { - case l@PredicateLookup(pred, psf, _) if psf == app && pred == snapPred => SortWrapper(mwsfTerm, l.sort) - }() - case _ => pcs - } - }) + pcs.transform { case l@PredicateLookup(`snapPred`, `app`, _) => SortWrapper(mwsfTerm, l.sort) }() + }) + case _ => pcsList + } (pcsListNew, if (transformed) Vector.empty else Vector(BuiltinEquals(mwsfTerm, snap))) } } - val (updatedPcs, mwsfTerms) = fromSnapTree(snapRhsUpdated, mwsfApply, conditionalizedPcs) - - // Combine all variables that will be quantified over - val quantifiedVars = abstractLhs +: newSnapshotMaps.filter(app => updatedPcs.exists(pcs => pcs.contains(app))).toSeq + val (updatedPcs, mwsfTerms) = substituteMwsfTerm(snapRhsUpdated, mwsfApply, conditionalizedPcs) - // Partition path conditions into a set which include the abstractLhs and those which do not - var (pcsWithQuantifiedVars, pcsWithoutQuantifiedVars) = updatedPcs.partition(pcs => quantifiedVars.exists(qv => pcs.contains(qv))) - - // Remove forall quantifiers with the quantified variable in `quantifiedVars` - pcsWithQuantifiedVars = pcsWithQuantifiedVars - .map(_.transform { - case Quantification(Forall, v :: Nil, body: Term, _, _, _, _) if v.equals(abstractLhs) => body - }(_ => true)) ++ mwsfTerms + // Partition path conditions into a set which include the abstractLhs or any new snapshot map and those which do not + val (pcsWithAbstractLhs, pcsWithoutAbstractLhs) = updatedPcs.partition(pcs => pcs.contains(abstractLhs)) // Combine all path conditions which include the abstractLhs and add it to the verifier's list of definitions - val pcsQuantified = Forall( - abstractLhs, - And(pcsWithQuantifiedVars), - Trigger(mwsfApply) - ) + val pcsQuantified = Forall(abstractLhs, And(pcsWithAbstractLhs ++ mwsfTerms), Trigger(mwsfApply)) v1.decider.assumeDefinition(pcsQuantified) - - // Return the summarized path conditions - pcsWithoutQuantifiedVars :+ pcsQuantified + pcsWithoutAbstractLhs :+ pcsQuantified } /** @@ -359,7 +374,7 @@ object magicWandSupporter extends SymbolicExecutionRules { ch: Chunk, pcs: RecordedPathConditions, abstractLhs: Var, - mwsf: Var, + mwsf: Term, snapRhs: Term, functionsBeforePackaging: Set[FunctionDecl], v5: Verifier): VerificationResult = { @@ -411,7 +426,8 @@ object magicWandSupporter extends SymbolicExecutionRules { v5.decider.prover.comment("Definitional axioms for singleton-SM's value") v5.decider.assumeDefinition(smValueDef) val ch = quantifiedChunkSupporter.createSingletonQuantifiedChunk(formalVars, wand, args, FullPerm, sm, s.program) - appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, mwsf, snapRhs, functionsBeforePackaging, v5) + val lookupMwsf = SortWrapper(ResourceLookup(wand, sm, args, s.program), sorts.MagicWandSnapFunction) + appendToRecordedBranches(s5, ch, v5.decider.pcs.after(preMark), abstractLhs, lookupMwsf, snapRhs, functionsBeforePackaging, v5) }) } else { val wandSnapshot = MagicWandSnapshot(mwsf) From b5ae0bac9e30f7276aef7b2b7381553c59d8affe Mon Sep 17 00:00:00 2001 From: Manuel Dublanc <19774382+manud99@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:22:15 +0200 Subject: [PATCH 43/43] Update silver. --- silver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silver b/silver index 2f8d64b4e..6047989e0 160000 --- a/silver +++ b/silver @@ -1 +1 @@ -Subproject commit 2f8d64b4e2e0d51b135f0d5a82a116be1bfb2095 +Subproject commit 6047989e0ed33e49ca608453f7813bdccdc96a2a