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

Stronger Map matching and selected map hooks #506

Merged
merged 11 commits into from
Feb 19, 2024

Conversation

jberthold
Copy link
Member

@jberthold jberthold commented Feb 14, 2024

Stronger Map matching

Previously, only fully concrete maps (all keys and values identical) would have matched, which is highly unlikely when matching simplification rules.
Now we compare the key sets for (syntactic) equality after substituting with the current substitution, and then try to perform matching on the remainder if a solution is at all possible. Most importantly, maps of fully-concrete keys can now be handled, matching the contained values. In addition, the case of matching a single unknown key out of a singleton map (i.e., without opaque rest in the subject map) is now possible.
Any ambiguous case is still rejected as MatchIndeterminate but some cases can be decided as MatchFailed immediately.

Selected hooks for Map

This change implements 3 crucial hooks for internalised Maps:

  • lookup: partial function that looks up a value for a given key in a map. Returns no result when the key is not present.
  • lookupOrDefault: total function that looks up a value for a given key in a map. If the key is not present, it returns a given default value instead.
    The implemented hook won't return any result when the presence of the key cannot be decided.
  • in_keys: predicate that checks whether a given key is present in a map. The implemented hook won't return any result if the presence of the key cannot be decided.

All three hooks are conservative, not returning any results when they find unevaluated (parts of the) arguments.

These hooks, together with the enhanced Map-matching of #502, avoid a fall-back to legacy kore-rpc in an issue reported in #498.
Alternatively, more suitable rules for in_keys could be added to domains.md, and the rules for lookup and lookupOrDefault could be marked as preserves-definedness so booster will use them.

Fixes #498

Previously, only fully concrete maps (all keys and values identical)
would have matched, which is highly unlikely when matching
simplification rules.

Now we compare the key sets for (syntactic) equality after
substituting with the current substitution, and then try to perform
matching on the remainder if a solution is at all possible.

Most importantly, maps of fully-concrete keys can now be handled,
matching the contained values. In addition, the case of matching a
single unknown key out of a singleton map (i.e., without opaque rest
in the subject map) is now possible.

Any ambiguous case is still rejected as `MatchIndeterminate` but some
cases can be decided as `MatchFailed` immediately.
As an alternative to modifying rules in `domains.md`, this change
implements 3 crucial hooks for internalised `Map`s:

* `lookup`: partial function that looks up a value for a given key in
  a map. Returns no result when the key is not present.
* `lookupOrDefault`: total function that looks up a value for a given
  key in a map. If the key is not present, it returns a given default
  value instead. The implemented hook won't return any result when the
  presence of the key cannot be decided.
* `in_keys`: predicate that checks whether a given key is present in a
  map. The implemented hook won't return any result if the presence of
  the key cannot be decided.

All three hooks are conservative, not returning any results when they
find unevaluated (parts of the) arguments.

These hooks, together with the enhanced `Map`-matching of #502, avoid
a fall-back to legacy `kore-rpc` in an issue reported in #498.

Alternatively, more suitable rules for `in_keys` could be added to
`domains.md`, and the rules for `lookup` and `lookupOrDefault` could
be marked as `preserves-definedness` so `booster` will use them.
@jberthold
Copy link
Member Author

Regression tests do not show any impact

KEVM regression tests

Test EXPERIMENT-selected-MAP-hooks time master-ac4575c time (EXPERIMENT-selected-MAP-hooks/master-ac4575c) time
kontrol/test-expectreverttest-testfail_expectrevert_bytes4-0-spec.k 53.82 68.41 0.7867270866832334
kontrol/test-emitcontracttest-testexpectemit-0-spec.k 74.12 78.84 0.9401319127346525
erc20/hkg/balanceOf-spec.k 52.73 55.09 0.9571610092575784
kontrol/test-expectreverttest-testfail_expectrevert_false-0-spec.k 83.4 86.93 0.9593926147474979
kontrol/test-expectreverttest-test_expectrevert_returnvalue-0-spec.k 68.27 54.81 1.2455756248859695
TOTAL 332.34 344.08 0.9658800279004882

kontrol regression

Test EXPERIMENT-selected-MAP-hooks time master-ac4575c time (EXPERIMENT-selected-MAP-hooks/master-ac4575c) time
BytesTypeTest.test_bytes4(bytes4) 23.3 25.16 0.9260731319554849
BlockParamsTest.testCoinBase() 26.13 27.38 0.954346238130022
BlockParamsTest.testChainId(uint256) 30.72 31.99 0.960300093779306
AddrTest.test_notBuiltinAddress_concrete() 18.36 19.03 0.9647924330005254
StoreTest.testGasLoadWarmUp() 61.33 59.11 1.0375570969379124
TOTAL 159.84 162.67 0.9826028155160754

@jberthold jberthold marked this pull request as ready for review February 14, 2024 10:17
@jberthold
Copy link
Member Author

Repeated measurement with PYTEST_PARALLEL=1 produced an empty file for kevm (no significant differences) and this for kontrol:

Test EXPERIMENT-selected-MAP-hooks time master-f0fc68c time (EXPERIMENT-selected-MAP-hooks/master-f0fc68c) time
StructTypeTest.test_vars((uint8,uint32,bytes32)) 25.62 29.09 0.8807150223444483
SymbolicStorageTest.testEmptyInitialStorage(uint256) 30.65 34.45 0.8896952104499273
src/tests/integration/test_foundry_prove.py::test_foundry_merge_nodes 39.71 44.02 0.9020899591094956
AssumeTest.test_assume_false(uint256,uint256) 67.88 75.14 0.9033803566675538
Setup2Test.test_setup() 29.76 32.87 0.905384849406754
LabelTest.testLabel() 23.41 25.84 0.9059597523219814
StoreTest.testLoadNonExistent() 33.79 37.16 0.9093110871905276
StoreTest.testStoreLoadNonExistent() 41.73 45.86 0.9099433057130396
WarpTest.test_warp_setup() 47.03 51.11 0.920172177656036
BytesTypeTest.test_bytes32(bytes32) 17.22 18.47 0.9323226854358418
ChainIdTest.test_chainid_setup() 48.11 51.33 0.9372686538086888
ExpectRevertTest.testFail_expectRevert_empty() 35.73 38.03 0.9395214304496449
ExpectRevertTest.test_expectRevert_true() 81.11 85.91 0.9441275753695728
RollTest.test_roll_setup() 50.14 52.91 0.9476469476469477
AssertTest.test_revert_branch(uint256,uint256) 76.23 80.37 0.948488241881299
MockCallTest.testSelectorMockCall() 41.01 43.2 0.9493055555555554
BlockParamsTest.testWarp(uint256) 28.93 30.46 0.9497701904136572
StoreTest.testGasLoadWarmUp() 57.24 59.99 0.9541590265044174
BlockParamsTest.testCoinBase() 24.61 25.64 0.9598283931357254
BlockParamsTest.testChainId(uint256) 28.99 27.96 1.036838340486409
LoopsTest.test_sum_10() 53.98 51.95 1.0390760346487005
AssertTest.test_failing_branch(uint256) 86.95 83.52 1.0410680076628354
PlainPrankTest.testFail_startPrank_internalCall() 42.0 40.11 1.0471204188481675
StoreTest.testStoreLoad() 57.36 54.08 1.0606508875739644
AssertTest.testFail_assert_true() 40.82 37.63 1.0847727876694127
AssumeTest.testFail_assume_false(uint256,uint256) 49.31 45.36 1.0870811287477955
StoreTest.testGasStoreColdVM() 35.1 32.15 1.0917573872472786
BytesTypeTest.test_bytes4(bytes4) 21.88 19.96 1.096192384769539
GasTest.testSetGas() 26.73 24.23 1.1031778786628146
UintTypeTest.test_uint256(uint256) 28.72 25.01 1.1483406637345062
src/tests/integration/test_foundry_prove.py::test_foundry_auto_abstraction 33.64 29.03 1.158801240096452
TOTAL 1305.39 1332.84 0.979404879805528

Copy link
Contributor

@geo2a geo2a left a comment

Choose a reason for hiding this comment

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

The implementation and test cases look comprehensive!

I think I would like to have two more things:

  • an integration tests that would have failed in booster-dev before this change and not succeeds;
  • a file in the docs/ directory with a short textual description of the matching algorithm. I find the inline comments in the implementation very insightful, but I also think that we need to document the matching (and unification) algorithms at a higher level.

The second item (docs) could be left for a follow-up PR.

@jberthold
Copy link
Member Author

The implementation and test cases look comprehensive!

I think I would like to have two more things:

  • an integration tests that would have failed in booster-dev before this change and not succeeds;

I have added integration tests in test-collectiontest. All new tests would fail with booster-dev from main.

  • a file in the docs/ directory with a short textual description of the matching algorithm. I find the inline comments in the implementation very insightful, but I also think that we need to document the matching (and unification) algorithms at a higher level.

The second item (docs) could be left for a follow-up PR.

OK, let's do this in a follow-up. We can maybe also document the hook implementation structure (but that's more developer-facing than the matching/unification overview)


mapLookupHook :: BuiltinFunction
mapLookupHook args
| [KMap _ pairs _mbRest, key] <- args =
Copy link
Contributor

Choose a reason for hiding this comment

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

Will we eventually have to return something like #bottom here in case we know this is a fully concrete map?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably that would be the correct way - we know the result is "undefined" so no point trying any equations.
Our types currently do not allow Terms to be #Bottom - we would need to add an explicit term-bottom constructor.

@jberthold jberthold merged commit cb34e5e into main Feb 19, 2024
5 checks passed
@jberthold jberthold deleted the EXPERIMENT-selected-MAP-hooks branch February 19, 2024 00:00
@jberthold jberthold mentioned this pull request Feb 19, 2024
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.

Booster falls back to the Haskell backend because of unevaluated functions.
3 participants