Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace assignments in invariants #1664

Merged
merged 9 commits into from
Apr 26, 2022
Merged

Replace assignments in invariants #1664

merged 9 commits into from
Apr 26, 2022

Conversation

konnov
Copy link
Collaborator

@konnov konnov commented Apr 25, 2022

Closes #1663. As discussed in #1663, the model checker fails when an invariant contains assignments, e.g., UNCHANGED x. The bugfix is very simple: All assignments x' := e are replaced back to x' = e in the invariant candidates.

Instead of implementing a yet another class similar to Prime and Deprime, I have refactored ReplaceFixed to accept a partial function, which produces a replacement, if the function is defined on its argument. To disambiguate three versions of apply, I have renamed them to whenEqualsTo, whenMatches, and withFun. This refactoring touched a lot of files, but the changes are syntactic.

  • Tests added for any new code
  • Ran make fmt-fix (or had formatting run automatically on all files edited)
  • Documentation added for any new functionality
  • Entry added to UNRELEASED.md for any new functionality

@konnov konnov requested a review from Kukovec April 25, 2022 20:42
@codecov-commenter
Copy link

codecov-commenter commented Apr 25, 2022

Codecov Report

Merging #1664 (919e659) into unstable (df647a9) will increase coverage by 0.02%.
The diff coverage is 82.14%.

❗ Current head 919e659 differs from pull request most recent head 844f9fb. Consider uploading reports for the commit 844f9fb to get more accurate results

@@             Coverage Diff              @@
##           unstable    #1664      +/-   ##
============================================
+ Coverage     74.96%   74.99%   +0.02%     
============================================
  Files           358      356       -2     
  Lines         11612    11597      -15     
  Branches        541      552      +11     
============================================
- Hits           8705     8697       -8     
+ Misses         2907     2900       -7     
Impacted Files Coverage Δ
...lache/tla/assignments/passes/PrimingPassImpl.scala 0.00% <0.00%> (ø)
...tla/bmcmt/passes/ReTLAToVMTTranspilePassImpl.scala 0.00% <0.00%> (ø)
.../at/forsyte/apalache/tla/imp/OpDefTranslator.scala 100.00% <ø> (ø)
.../scala/at/forsyte/apalache/tla/pp/Normalizer.scala 76.59% <0.00%> (ø)
...la/at/forsyte/apalache/tla/bmcmt/VCGenerator.scala 69.09% <80.00%> (-0.73%) ⬇️
...t/forsyte/apalache/tla/bmcmt/SeqModelChecker.scala 90.56% <100.00%> (ø)
...tla/bmcmt/trex/ConstrainedTransitionExecutor.scala 97.05% <100.00%> (ø)
.../forsyte/apalache/tla/pp/ConstAndDefRewriter.scala 81.25% <100.00%> (ø)
...ala/at/forsyte/apalache/tla/pp/ExprOptimizer.scala 90.12% <100.00%> (ø)
...ain/scala/at/forsyte/apalache/tla/pp/Inliner.scala 92.06% <100.00%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update df647a9...844f9fb. Read the comment docs.

Copy link
Collaborator

@Kukovec Kukovec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +109 to +115
case ex @ NameEx(name) if vars.contains(name) =>
// add prime to a variable from vars
OperEx(TlaActionOper.prime, ex)(ex.typeTag)

case OperEx(TlaActionOper.prime, primedOnce @ OperEx(TlaActionOper.prime, _)) =>
// in case the variable was already primed, remove the second prime
primedOnce
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than defining de-priming of doubly primed expressions, which relies on the implementation detail that ReplaceFixed implements post-transform too
https://github.com/informalsystems/apalache/blob/919e65948904156615a05980d98b335fd5a8fc2a/tlair/src/main/scala/at/forsyte/apalache/tla/lir/transformations/standard/ReplaceFixed.scala#L96-L99
I'd prefer defining this in a way that never attempts to add unnecessary primes in the first place:

Suggested change
case ex @ NameEx(name) if vars.contains(name) =>
// add prime to a variable from vars
OperEx(TlaActionOper.prime, ex)(ex.typeTag)
case OperEx(TlaActionOper.prime, primedOnce @ OperEx(TlaActionOper.prime, _)) =>
// in case the variable was already primed, remove the second prime
primedOnce
case ex@OperEx(TlaActionOper.prime, _) =>
// in case the expression was already primed, ignore
ex
case ex @ NameEx(name) if vars.contains(name) =>
// add prime to a variable from vars
OperEx(TlaActionOper.prime, ex)(ex.typeTag)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that this code would not introduce two primes, i.e., by going to 113-115 first and then to 109-111 one level up in the recursion?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it goes one level up it will already be primed, so 109 will skip.

x -(113)-> x' -(109)-> x'
x' -(109)-> x' -(109)-> x'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into it, seems like your implementation of withFun doesn't allow short-circuiting.
Unlike a dedicated transformation, like Prime, it must always first look into the generic OperEx case
https://github.com/informalsystems/apalache/blob/919e65948904156615a05980d98b335fd5a8fc2a/tlair/src/main/scala/at/forsyte/apalache/tla/lir/transformations/standard/ReplaceFixed.scala#L96-L99
This is potentially problematic in practice; a lot of transformations rely on the fact that certain synactic forms halt exploration, because transforming may ruin subexpressions, but these generic PartialFunction traversals do not, they always full explore the expression tree.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Prime had a very special behavior. We can think of generalizing ReplaceFixed even further.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just Prime, a typical tx has cases like

case InterestingCase => interestingCaseTx
case OperEx(args) => mapOverArgs
case LetIn => mapOverDecls
case default => defaultTx

where interestingCaseTx would typically not recurse any more. I think this can be fixed by changing

    ex match {
      case LetInEx(body, defs @ _*) =>
        // Transform bodies of all op.defs
        val newDefs = defs.map(tracker.trackOperDecl { d => d.copy(body = self(d.body)) })
        val newBody = self(body)
        val retEx = if (defs == newDefs && body == newBody) ex else LetInEx(newBody, newDefs: _*)(ex.typeTag)
        tr(retEx)


      case OperEx(op, args @ _*) =>
        val newArgs = args.map(self)
        val retEx = if (args == newArgs) ex else OperEx(op, newArgs: _*)(ex.typeTag)
        tr(retEx)


      case _ => tr(ex)

in withFun to

    ex match {
      case special if partialFun.isDefinedAt(special) =>
        partialFun(special)

      case LetInEx(body, defs @ _*) =>
        // Transform bodies of all op.defs
        val newDefs = defs.map(tracker.trackOperDecl { d => d.copy(body = self(d.body)) })
        val newBody = self(body)
        val retEx = if (defs == newDefs && body == newBody) ex else LetInEx(newBody, newDefs: _*)(ex.typeTag)
        retEx

      case OperEx(op, args @ _*) =>
        val newArgs = args.map(self)
        val retEx = if (args == newArgs) ex else OperEx(op, newArgs: _*)(ex.typeTag)
        retEx

      case _ => ex

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It got automerged. Shall we open another PR for your suggestions?

konnov and others added 2 commits April 26, 2022 13:05
…ns/package.scala

Co-authored-by: Kukovec <jure.kukovec@gmail.com>
@konnov konnov enabled auto-merge April 26, 2022 11:10
@konnov konnov merged commit d4e464c into unstable Apr 26, 2022
@konnov konnov deleted the ik/unchanged1663 branch April 26, 2022 16:17
This was referenced May 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error when checking UNCHANGED in an invariant
3 participants