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

3956 branching on complete conditions in Booster #4058

Merged
merged 51 commits into from
Oct 29, 2024

Conversation

geo2a
Copy link
Collaborator

@geo2a geo2a commented Oct 15, 2024

Part of #3956
Partially subsumes #3960

This PR makes the rewrite rule application algorithm of Booster more powerful by allowing branching on complete conditions.

The rewriting algorithm is modified:

  • when checking the requires clause of a rule, we compute the remainder condition RULE_REM of that attempted, which is the semantically-checked subset of the required conjuncts P which unclear after checking its entailment form the pattern's constrains PC, meaning that (PC /\ P, PC /\ not P) is (SAT, SAT) or any of the two queries were UNKNOWN
  • if that remainder is not empty, we return it's negation together with the result
  • when we are finished attempting a priority group of rules, we collect the negated remainder conditions not RULE_REM_i and conjunct them. This the groups remainder condition GROUP_REM == not RULE_REM_1 /\ not RULE_REM_2 /\ ... /\ not RULE_REM_n
  • At that point, we need to check GROUP_REM for satisfiablity.
    • If the GROUP_REM condition is UNSAT, it means that this group of rules is complete, meaning that no other rule can possibly apply, and we do not need to even attempt applying rules of lower priority. This behaviour is the primary contribution of this PR.
    • Otherwise, if it is SAT or solver said UNKNOWN, it means that this group of rules is not complete, i.e. it does not cover the whole space of logical possibility, and we need to construct a remainder configuration, and continue attempting to apply other rules to it. If no rules remain, it means that we are stuck and the semantics is incomplete. This PR does not implement the process of descending into the remainder branch. Booster with this PR will abort on a SAT remainder.

This behaviour is active by default in booster-dev and can be enabled in kore-rpc-booster with the flag --fallback-on Aborted,Stuck (the default is Aborted,Stuck,Branching). Note that with the default reasons, the behaviour of kore-rpc-booster is functionally the same, but operationally slightly different: In Proxy.hs, Booster may not return Branching, and the fallback logic will confirm that Kore returns Branching as well, flagging any differences in the [proxy] logs (see Proxy.hs#L391).

TODO: we need a better flag to enabled branching in Booster, as giving a --fallback-on Aborted,Stuck is really non-intuitive.

Note:
a naive algorithm to compute the remainder conditions would be: after applying a group of rules, take their substituted requires clauses, disjunct and negate. However, this yields a non-minimal predicate that was not simplified and syntactically filtered, potentially making it harder for the SMT solver to solve. The algorithm described above and implemented in this PR re-uses the indeterminate results obtained while applying individual rules and simplifying/checking their individual requires clauses. This logic has been originally proposed by Sam in #3960.

See remainder-predicates.k and test-remainder-predicates for a series of integrations tests that cover the behaviour of booster-dev and kore-rpc-booster.

@geo2a geo2a force-pushed the 3956-booster-rewrite-rule-remainders branch 6 times, most recently from 0935540 to 3ba2063 Compare October 17, 2024 15:14
@geo2a geo2a force-pushed the 3956-booster-rewrite-rule-remainders branch from 13f5fc8 to 82c6764 Compare October 22, 2024 14:05
@geo2a geo2a mentioned this pull request Oct 22, 2024
geo2a added a commit that referenced this pull request Oct 23, 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]>
@geo2a geo2a force-pushed the 3956-booster-rewrite-rule-remainders branch 4 times, most recently from 711637d to 8994bcb Compare October 25, 2024 13:27
@geo2a geo2a force-pushed the 3956-booster-rewrite-rule-remainders branch from 8994bcb to 36419fa Compare October 25, 2024 13:28
@geo2a geo2a changed the title 3956 booster rewrite rule remainders 3956 branching on complete conditions in Booster Oct 25, 2024
@geo2a geo2a requested a review from jberthold October 25, 2024 14:51
@geo2a geo2a marked this pull request as ready for review October 25, 2024 14:52
@ehildenb
Copy link
Member

One thing unclear from the PR description: in the case where the group of rules is incomplete, but htere are no other rules in lower priority buckets, do we still fallback to Kore or do we return the branching + the branch into the stuck state with the remainder?

Also, it would be good to post performance numbers for this if possible. Are there any tests which exercise this new code in KEVM or Kontrol?

booster/library/Booster/JsonRpc.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
@geo2a
Copy link
Collaborator Author

geo2a commented Oct 28, 2024

No difference in KEVM performance at 691f19c

| Test                               | 3956-booster-rewrite-rule-remainders time | master-aaf0ef51a time | (3956-booster-rewrite-rule-remainders/master-aaf0ef51a) time
|------------------------------------|-------------------------------------------|-----------------------|--------------------------------------------------------------
| mcd/flopper-file-pass-rough-spec.k | 19.06                                     | 18.33                 | 1.0398254228041461
| examples/sum-to-n-spec.k           | 102.11                                    | 94.96                 | 1.0752948609941029
| TOTAL                              | 121.17                                    | 113.28999999999999    | 1.0695560067084475

Test with the 3956-booster-rewrite-rule-remainders branch were ran with KORE_RPC_OPTS='--fallback-on Aborted'.

Note that the KEVM test suite does not show a performance improvement because the proofs almost don't branch.

@geo2a
Copy link
Collaborator Author

geo2a commented Oct 28, 2024

I anticipate the BMC-proofs of looping code to benefit the most. For example, I ran the BMCLoopsTest.test_bmc(uint256) test with --bmc-depth 10 with kore-rpc-booster from Kontrol master and from this branch:

The version from Kontrol master took 5m 21s:

poetry run kontrol prove --match-test 'BMCLoopsTest.test_bmc(uint256)' --bmc-depth 10 --reinit
✨ PROOF PASSED ✨ test%BMCLoopsTest.test_bmc(uint256):12
⏳ Time: 5m 21s ⏳

The version from this branch took 4m 13s:

KORE_RPC_OPTS='--fallback-on Aborted' poetry run kontrol prove --match-test 'BMCLoopsTest.test_bmc(uint256)' --bmc-depth 10 --reinit --kore-rpc-command "$KRB_REM"
✨ PROOF PASSED ✨ test%BMCLoopsTest.test_bmc(uint256):11
⏳ Time: 4m 13s ⏳

Note KORE_RPC_OPTS='--fallback-on Aborted' which disables fallbacks to Kore on Branching and Stuck results in Proxy.hs.

Running the Kontrol performance tests now, though I expect the difference there to show less prominently, as most of the tests are short-running, and the BMC tests are ran with --bmc-depth 3.

@geo2a
Copy link
Collaborator Author

geo2a commented Oct 28, 2024

Kontrol tests are extremely noisy and have several tests that show up as failing on both master and the feature branch. These seem to be expected failures. I think they contribute to the noise very much, as they are short-running and a minor difference in the server response may trigger a relatively significant slowdown.

Feature branch was ran with KORE_RPC_OPTS='--fallback-on Aborted'.

Test 3956-booster-rewrite-rule-remainders time master-aaf0ef51a time (3956-booster-rewrite-rule-remainders/master-aaf0ef51a) time
ImmutableVarsTest.test_run_deployment(uint256) 148.39 216.03 0.6868953386103781
kontrol/src/tests/integration/test_foundry_prove.py::test_constructor_with_symbolic_args 60.44 83.55 0.7233991621783363
ExternalLibTest.testSquare(uint256) 37.49 45.29 0.8277765511150365
FreshCheatcodes.test_address() 18.41 22.2 0.8292792792792794
ExpectRevertTest.testFail_expectRevert_empty() 17.69 21.13 0.8371982962612401
PlainPrankTest.test_startPrankWithOrigin_true() 25.48 29.86 0.8533154722036169
EmitContractTest.testExpectEmit() 21.95 24.83 0.8840112766814338
ExpectRevertTest.test_ExpectRevert_increasedDepth() 27.01 30.27 0.8923026098447309
EmitContractTest.testExpectEmitLessTopics() 22.13 24.74 0.8945028294260308
ExpectRevertTest.test_expectRevert_message() 26.24 28.78 0.9117442668519804
CounterTest.testIncrement() 27.26 29.5 0.9240677966101696
MockCallTestFoundry.testMockCalldata() 50.7 54.52 0.92993396918562
MockFunctionTest.test_mock_function() 62.44 67.1 0.9305514157973175
AssertTest.test_assert_false() 38.3 40.99 0.9343742376189313
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_merge_nodes 40.51 43.32 0.9351338873499537
AssertTest.testFail_assert_true() 38.63 40.63 0.950775289195176
HevmTests.prove_require_assert_true 42.03 44.12 0.9526291931097008
AssumeTest.test_assume_false(uint256,uint256) 58.29 60.77 0.9591903899950633
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_refute_node 9.64 10.04 0.9601593625498009
SymbolicStorageTest.testFail_SymbolicStorage1(uint256) 42.34 44.04 0.9613987284287012
AssertTest.test_revert_branch(uint256,uint256) 61.39 63.64 0.9646448774355751
AssertTest.test_assert_true() 46.47 48.11 0.9659114529203907
ExpectRevertTest.testFail_expectRevert_bytes4() 32.52 30.02 1.08327781479014
UintTypeTest.test_uint256(uint256) 18.89 17.39 1.0862564692351926
FreshBytesTest.test_symbolic_bytes_length 29.08 26.68 1.0899550224887555
CounterTest.testSetNumber(uint256) 32.35 29.63 1.0917988525143436
ExpectRevertTest.test_expectRevert_returnValue() 33.06 30.16 1.0961538461538463
ExpectRevertTest.testFail_expectRevert_multipleReverts() 27.93 25.29 1.1043890865954924
EmitContractTest.testExpectEmitDoNotCheckData() 24.46 22.1 1.1067873303167421
ExpectRevertTest.test_expectRevert_bytes4() 27.84 25.09 1.1096054204862495
MockCallTestFoundry.testRevertMock() 38.92 34.91 1.1148668003437412
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_auto_abstraction 51.72 45.59 1.1344593112524675
PlainPrankTest.test_prank_zeroAddress_true() 32.23 27.81 1.15893563466379
FreshCheatcodes.test_int128() 21.49 18.17 1.182718767198679
GasTest.testSetGas() 21.0 17.74 1.1837655016910937
TOTAL 1314.72 1424.04 0.9232324934692846

@geo2a
Copy link
Collaborator Author

geo2a commented Oct 28, 2024

@ehildenb answering your questions:

in the case where the group of rules is incomplete, but htere are no other rules in lower priority buckets, do we still fallback to Kore or do we return the branching + the branch into the stuck state with the remainder?

if any rules apply but there is a SAT remainder predicate, then Booster will abort rewriting and we will get a fallback to Kore in kore-rpc-booster, regardless of any lower priority groups. The code in this PR will never create a remainder branch, this is left as future work. Out purpose is to prove that the remainder branch is not feasible.

Also, it would be good to post performance numbers for this if possible. Are there any tests which exercise this new code in KEVM or Kontrol?

I've posted the numbers for KEVM and Kontrol. Unfortunately, neither test suite is representative, and I'd argue that the Kontrol results may not be 100% reliable. I've manually ran the BMCLoopsTest.test_bmc(uint256) proof with --bmc-depth 10 which clearly shows the performance benefit. Any proofs that branches a lot and is relatively long should benefit too.

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]>
booster/library/Booster/JsonRpc.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Show resolved Hide resolved
booster/library/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
@@ -2,7 +2,6 @@ set -eux

SCRIPT_DIR=$(dirname $0)
PLUGIN_DIR=${PLUGIN_DIR:-""}
SECP=${SECP256K1_DIR:-/usr}
Copy link
Member

Choose a reason for hiding this comment

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

We also still have this in foundry-bug-report.kompile, BTW...

booster/unit-tests/Test/Booster/Pattern/Rewrite.hs Outdated Show resolved Hide resolved
@geo2a geo2a merged commit 328cd2c into master Oct 29, 2024
6 checks passed
@geo2a geo2a deleted the 3956-booster-rewrite-rule-remainders branch October 29, 2024 12:31
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.

3 participants