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

4059 ensures substitution #4060

Merged
merged 36 commits into from
Oct 23, 2024
Merged

4059 ensures substitution #4060

merged 36 commits into from
Oct 23, 2024

Conversation

geo2a
Copy link
Collaborator

@geo2a geo2a commented Oct 15, 2024

Fixes #4059

This PR improves Booster's handling of substitutions.

Summary

Substitutions are special predicates: equations where the LHS is a variable, for example VAR ==Int TERM. See Booster.Syntax.Json.Internalise.mbSubstitution for the exact specification of what is considered to be a substitution.

This PR changes the Pattern type to explicitly carry these special substitution predicates:

+type Substitution = Map Variable Term
+
 -- | A term (configuration) constrained by a number of predicates.
 data Pattern = Pattern
     { term :: Term
     , constraints :: !(Set Predicate)
+    , substitution :: Substitution
     , ceilConditions :: ![Ceil]
     }
     deriving stock (Eq, Ord, Show, Generic, Data)

Substitution may appear out of three places:

The first source is handled by the pattern internalisation code. The second and third sources are different, as the pattern has already been internalised. All this sources can also produce generic (i.e. non-substitution) constrains that should be added into the constrains set of a Pattern. Substitutions, when produces, should be applied to the term of the Pattern and added to the substitution field. This PR makes sure we use the same code path for separating substitutions from generic predicates everywhere. We use Booster.Syntax.Json.Internalise.mbSubstitution.mkSubstitution to take care of cyclic, which are broken up, and duplicate bindings, which are treated as constraints.

With these changes, we can harmonize many (but not all) specialisations of the integrations test responses, i.e. we do not need many of the *.booster-dev and *.kore-rpc-dev response files.

Changes to pattern simplification code

As the Pattern type has changed, we must adapt the ApplyEquations.evaluatePattern and ApplyEquations.evaluatePattern' functions to:

  • consider substitutions as known truth together with all other constraints (that's what we did before)
  • simplify the substitution

We achieve that by doing the following:

  • convert the substitution into equalities and assume it as know truth
  • when constructing the new, simplified pattern, use the same code as when internalising a pattern to partition predicates into the substitution and non-substitution ones

Changes to rewrite rule application code

The Pattern.Rewrite.applyRule function has been changed to:

  • consider substiontion as known truth together with all other constraints (that's what we did before) when checking requires/ensures
  • extract the substitution from the ensured conditions and add it to the rewritten pattern

@geo2a geo2a force-pushed the 4059-ensures-substitution branch 3 times, most recently from 9b4f310 to bc44927 Compare October 17, 2024 09:46
booster/library/Booster/Pattern/Bool.hs Outdated Show resolved Hide resolved
booster/library/Booster/Syntax/ParsedKore/Internalise.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Bool.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
@geo2a geo2a force-pushed the 4059-ensures-substitution branch 2 times, most recently from 2e7f126 to 52d3ad4 Compare October 17, 2024 13:55
@geo2a
Copy link
Collaborator Author

geo2a commented Oct 17, 2024

Failed KEVM tests as of 52d3ad4

FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/flopper-dent-guy-diff-tic-not-0-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/flopper-dent-guy-same-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/flopper-kick-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/cat-file-addr-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/pot-join-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/end-pack-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/flapper-yank-pass-rough-spec.k]
FAILED src/tests/integration/test_prove.py::test_prove_rules[mcd/flopper-cage-pass-spec.k]

@geo2a geo2a force-pushed the 4059-ensures-substitution branch 5 times, most recently from 4e34924 to fdf62fc Compare October 22, 2024 09:05
@geo2a
Copy link
Collaborator Author

geo2a commented Oct 22, 2024

No failed KEVM tests at commit fdf62fc and not much difference in performance:

| Test                                          | 4059-ensures-substitution time | master-434710529 time | (4059-ensures-substitution/master-434710529) time
|-----------------------------------------------|--------------------------------|-----------------------|---------------------------------------------------
| mcd/vow-fess-fail-rough-spec.k                | 108.68                         | 115.11                | 0.9441403874554775
| mcd-structured/flopper-file-pass-rough-spec.k | 18.23                          | 19.14                 | 0.9524555903866249
| erc20/ds/allowance-spec.k                     | 52.94                          | 55.03                 | 0.9620207159731056
| benchmarks/storagevar01-spec.k                | 49.16                          | 47.2                  | 1.0415254237288134
| mcd/flopper-file-pass-rough-spec.k            | 19.46                          | 18.58                 | 1.047362755651238
| mcd-structured/cat-exhaustiveness-spec.k      | 118.05                         | 111.06                | 1.0629389519178822
| TOTAL                                         | 366.52000000000004             | 366.12                | 1.0010925379656943

@geo2a geo2a force-pushed the 4059-ensures-substitution branch from 8e622f9 to 598c8a4 Compare October 22, 2024 12:29
@geo2a geo2a force-pushed the 4059-ensures-substitution branch from 0c612f3 to b2ea0f8 Compare October 22, 2024 13:46
@geo2a geo2a requested a review from jberthold October 22, 2024 15:48
@geo2a geo2a marked this pull request as ready for review October 22, 2024 15:48
Copy link
Member

@jberthold jberthold left a comment

Choose a reason for hiding this comment

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

Big job, well done! I like that we can ditch many of the response*.booster-dev files.

One open question is the removal of purging the simplifier cache when we have new constraints, I think that should still be done. Please put this back or explain why we don't.

The other things are minor and stylistic. Maybe we should think about the pattern constructor, as mentioned.

Comment on lines 253 to 259
let substPat =
Pattern
{ term = substituteInTerm substitution pat.term
, constraints = Set.map (substituteInPredicate substitution) pat.constraints
{ term = Substitution.substituteInTerm pat.substitution pat.term
, constraints = Set.map (Substitution.substituteInPredicate pat.substitution) pat.constraints
, ceilConditions = pat.ceilConditions
, substitution = pat.substitution
}
Copy link
Member

Choose a reason for hiding this comment

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

While reading through here, I thought that internaliseTermOrPredicates, and any internalisation that may return a Pattern as a whole, should maybe already apply the substitution before returning the result (somewhat "normalising" the pattern before it is returned).
The internalisePattern, in contrast, returns the parts separately and leaves it to the caller to piece them back together. This is because we use it a lot in the definition internalisation where we need the parts separately.
This is more of a stylistic preference, and IDK how much fall-out it will have, so it is not necessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a very good point. I'll try putting this change in.

Comment on lines -188 to +190
withPatternContext Pattern{term, constraints} m =
let t' = foldl' AndTerm term $ Set.toList $ Set.map coerce constraints -- FIXME
withPatternContext Pattern{term, constraints, substitution} m =
let t' = foldl' AndTerm term . map coerce $ Set.toList constraints <> asEquations substitution
Copy link
Member

Choose a reason for hiding this comment

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

That FIXME was referring to the fact that the t' :: Term is not well-sorted. Probably good enough for logging, though... just that nobody should use the json version of it as input directly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How do we make it well-sorted? Replace AndTerm with _andBool_?

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 no, there's a term there...

Comment on lines 113 to 125
Pattern
{ term = substituteInTerm substitutionL patL.term
, constraints = Set.map (substituteInPredicate substitutionL) patL.constraints
{ term = Substitution.substituteInTerm substitutionL patL.term
, constraints = Set.map (Substitution.substituteInPredicate substitutionL) patL.constraints
, ceilConditions = patL.ceilConditions
, substitution = substitutionL
}
substPatR =
Pattern
{ term = substituteInTerm substitutionR patR.term
, constraints = Set.map (substituteInPredicate substitutionR) patR.constraints
{ term = Substitution.substituteInTerm substitutionR patR.term
, constraints = Set.map (Substitution.substituteInPredicate substitutionR) patR.constraints
, ceilConditions = patR.ceilConditions
, substitution = substitutionR
}
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above, maybe the Pattern returned from internalisePatternOrTopOrBottom should already have the substitution applied...
I wonder whether we could just enforce that by a smart constructor for Pattern.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We very often construct a pattern directly, bypassing the Pattern_ smart-constructor. I'd love to have a smart constructor that is used consistently, but I think it may be out-of-scope for this PR. I do agree that it's best to apply the substitution in the internalisation function that return a Pattern.

Comment on lines 440 to 444
toCheck <-
lift $
filterOutKnownConstraints
(pat.constraints <> (Set.fromList . asEquations $ pat.substitution))
ruleRequires
Copy link
Member

Choose a reason for hiding this comment

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

(nit) maybe worth giving this expression a name:

Suggested change
toCheck <-
lift $
filterOutKnownConstraints
(pat.constraints <> (Set.fromList . asEquations $ pat.substitution))
ruleRequires
let knownConstraints = pat.constraints <> Set.fromList (asEquations pat.substitution)
toCheck <- lift $ filterOutKnownConstraints knownConstraints ruleRequires

and onwards below just inserting knownConstraints for pat.constraints in the old code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea!

Comment on lines -483 to -488
-- if a new constraint is going to be added, the equation cache is invalid
unless (null newConstraints) $ do
withContextFor Equations . logMessage $
("New path condition ensured, invalidating cache" :: Text)

lift . RewriteT . lift . modify $ \s -> s{equations = mempty}
Copy link
Member

Choose a reason for hiding this comment

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

(Why) Is this not true any more?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have a similar piece of code in applyRule, just after the call to checkEnsures. On master we are invalidating the cache twice: at the end of checkEnsures (removed here) and right after the call to checkEnsures in applyRule.

modifyVariables :: (Variable -> Variable) -> Pattern -> Pattern
modifyVariables f p =
Pattern
{ term = modifyVariablesInT f p.term
, constraints = Set.map (modifyVariablesInP f) p.constraints
, substitution = Map.map (coerce $ modifyVariablesInT f) p.substitution
Copy link
Member

Choose a reason for hiding this comment

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

This function is currently unused but we should either delete it or also modify the Map keys (variables).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed, thanks!

Comment on lines 38 to 39
-- substitution mining
extractSubsitution,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
-- substitution mining
extractSubsitution,
-- substitution mining
extractSubstitution,

😁

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

🤦

Comment on lines 510 to 521
extractSubsitution ::
[Internal.Predicate] -> (Map Internal.Variable Internal.Term, Set Internal.Predicate)
extractSubsitution ps =
let (potentialSubstituion, otherPreds) = partitionSubstitutionPreds . map mbSubstitution . coerce $ ps
(newSubstitution, leftoverPreds) = mkSubstitution potentialSubstituion
in (newSubstitution, Set.fromList $ leftoverPreds <> map unsafeFromBoolPred otherPreds)
where
partitionSubstitutionPreds = foldl' accumulate ([], [])
where
accumulate (accSubst, accOther) = \case
pSubst@SubstitutionPred{} -> (pSubst : accSubst, accOther)
pOther -> (accSubst, pOther : accOther)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe simpler to just use partition and a helper that extracts the Terms from BoolPred immediately (avoids unsafeFromBoolPred below)

Suggested change
extractSubsitution ::
[Internal.Predicate] -> (Map Internal.Variable Internal.Term, Set Internal.Predicate)
extractSubsitution ps =
let (potentialSubstituion, otherPreds) = partitionSubstitutionPreds . map mbSubstitution . coerce $ ps
(newSubstitution, leftoverPreds) = mkSubstitution potentialSubstituion
in (newSubstitution, Set.fromList $ leftoverPreds <> map unsafeFromBoolPred otherPreds)
where
partitionSubstitutionPreds = foldl' accumulate ([], [])
where
accumulate (accSubst, accOther) = \case
pSubst@SubstitutionPred{} -> (pSubst : accSubst, accOther)
pOther -> (accSubst, pOther : accOther)
extractSubstitution ::
[Internal.Predicate] -> (Map Internal.Variable Internal.Term, Set Internal.Predicate)
extractSubstitution ps =
let (potentialSubstitution, otherPreds) = partitionSubstitutionPreds . map mbSubstitution . coerce $ ps
(newSubstitution, leftoverPreds) = mkSubstitution potentialSubstitution
in (newSubstitution, Set.fromList $ leftoverPreds <> otherPreds)
where
partitionSubstitutionPreds = partitionEithers . unpackBoolPred
where
unpackBoolPred = \case
pSubst@SubstitutionPred{} -> Left pSubst
BoolPred p -> Right p
other -> error $ "extractSubstitution: unsupported predicate " <> show other

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great idea, done!


-- if a new constraint is going to be added, the equation cache is invalid
unless (null newConstraints) $ do
unless (null ensuredConditions) $ do
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We invalidate the cache here if a new constraint has been ensured.

@geo2a geo2a merged commit aaf0ef5 into master Oct 23, 2024
6 checks passed
@geo2a geo2a deleted the 4059-ensures-substitution branch October 23, 2024 10:25
geo2a added a commit that referenced this pull request Oct 28, 2024
Fixes #4059

This PR improves Booster's handling of substitutions.

## Summary

Substitutions are special predicates: equations where the LHS is a
variable, for example `VAR ==Int TERM`. See
`Booster.Syntax.Json.Internalise.mbSubstitution` for the exact
specification of what is considered to be a substitution.

This PR changes the `Pattern` type to explicitly carry these special
substitution predicates:

```diff
+type Substitution = Map Variable Term
+
 -- | A term (configuration) constrained by a number of predicates.
 data Pattern = Pattern
     { term :: Term
     , constraints :: !(Set Predicate)
+    , substitution :: Substitution
     , ceilConditions :: ![Ceil]
     }
     deriving stock (Eq, Ord, Show, Generic, Data)
```

Substitution may appear out of three places:
- sent in the request body
- ensured as a post condition of a rewrite rule
- **NOT IMPLEMENTED** learned from the branching condition --- this is
something that will be added as part of #4058

The first source is handled by the pattern internalisation code. The
second and third sources are different, as the pattern has already been
internalised. All this sources can also produce generic (i.e.
non-substitution) constrains that should be added into the `constrains`
set of a `Pattern`. Substitutions, when produces, should be applied to
the `term` of the `Pattern` and added to the `substitution` field. This
PR makes sure we use the same code path for separating substitutions
from generic predicates everywhere. We use
`Booster.Syntax.Json.Internalise.mbSubstitution.mkSubstitution` to take
care of cyclic, which are broken up, and duplicate bindings, which are
treated as constraints.

With these changes, we can harmonize many (but not all) specialisations
of the integrations test responses, i.e. we do not need many of the
`*.booster-dev` and `*.kore-rpc-dev` response files.

## Changes to pattern simplification code

As the `Pattern` type has changed, we must adapt the
`ApplyEquations.evaluatePattern` and `ApplyEquations.evaluatePattern'`
functions to:
- consider `substitutions` as known truth together with all other
constraints (that's what we did before)
- simplify the substitution

We achieve that by doing the following:
- convert the substitution into equalities and assume it as know truth
- when constructing the new, simplified pattern, use the same code as
when internalising a pattern to partition predicates into the
substitution and non-substitution ones

## Changes to rewrite rule application code

The `Pattern.Rewrite.applyRule` function has been changed to:
- consider `substiontion` as known truth together with all other
constraints (that's what we did before) when checking requires/ensures
- extract the substitution from the ensured conditions and add it to the
rewritten pattern

---------

Co-authored-by: github-actions <[email protected]>
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.

Recognise ensured constraints as substitutions and apply them in Booster
2 participants