Skip to content

3863 llvm term cache #3882

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

Merged
merged 5 commits into from
May 22, 2024
Merged

3863 llvm term cache #3882

merged 5 commits into from
May 22, 2024

Conversation

jberthold
Copy link
Member

This PR introduces a cache for the terms returned from calls to Booster.LLVM.Internal.API.simplify. Unpacking now uses a term store for the unpacked terms, and recognises shared terms by a shallow index into this store, using TermF Int as the map key (where the Int are indexes of symbol application/injection arguments in the term store).

In small targeted tests using requests from MX-backend proofs , memory consumption was noticeably reduced, also resulting in better performance.

Currently, the cache only lives for the duration of one LLVM call (not across different calls), no global variables or unsafe IO is required.

Fixes #3863

jberthold added 5 commits May 21, 2024 15:46
This PR introduces a cache for the terms returned from calls to
`Booster.LLVM.Internal.API.simplify`. Unpacking now uses a term store
for the unpacked terms, and recognises shared terms by a shallow index
into this store, using `TermF Int` as the map key (where the `Int` are
indexes of symbol application/injection arguments in the term store).

In small targeted tests using requests from MX-backend proofs , memory
consumption was noticeably reduced, also resulting in better performance.
@jberthold
Copy link
Member Author

KEVM performance run

Test 3863-llvm-term-cache time master-0f3148308 time (3863-llvm-term-cache/master-0f3148308) time
erc20/hkg/balanceOf-spec.k 35.76 47.92 0.7462437395659431
mcd/end-cash-pass-rough-spec.k 187.89 224.16 0.838195931477516
mcd/pot-join-pass-rough-spec.k 168.3 197.18 0.8535348412617912
erc20/hkg/approve-spec.k 37.35 42.92 0.8702236719478099
erc20/hkg/totalSupply-spec.k 35.08 40.09 0.8750311798453478
erc20/hkg/transfer-failure-1-spec.k 45.69 52.1 0.8769673704414587
mcd/vat-addui-fail-rough-spec.k 56.21 60.77 0.9249629751522133
TOTAL 566.28 665.14 0.8513696364675106

kontrol performance test cannot be run at the moment.

@jberthold jberthold marked this pull request as ready for review May 22, 2024 04:45
= BTerm Term
| BPredicate Predicate
= BTerm Idx
| BPredicate Idx -- Predicate -- problem?!?
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the problem here that we will use the same store for terms and predicates in resolve -> mkSymbolApplication? I think this is fine, as the terms and predicates indeed consist of the same bits and pieces ultimately.

Copy link
Member Author

Choose a reason for hiding this comment

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

I should remove that comment, it was not a problem after all, we just wrap the Bool term into a Predicate at the end instead of during the procedure.

DomainValueF sort payload -> DomainValue sort payload
VarF v -> Var v
InjectionF s1 s2 i -> Injection s1 s2 (fromStore i)
other -> error $ "Unexpected shallow term " <> show other
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm feeling a little uneasy about this call to error, but I do agree that it will not ever be triggered by construction of the shallow terms in resolve -> mkSymbolApplication.

_ -> fail "Expecting a single term on the top of the stack"
runDecodeM version mDef $
decodeBlock mbSize >>= \case
[BTerm trmIdx] -> fmap stripRawTerm $ getTerm trmIdx
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like magic to me!

We have a single call to decodeTerm in the Booster.LLVM module, as far as I know. Do I understand correctly that the cache lifetime is confined to decodeTerm'?

Also, the getTerm trmIdx call will return the fully resolved term? The subterms of that term may be present in the store before, but only once?

I think I know the answers, just want a confirmation.

Big brain @jberthold !

Copy link
Member Author

Choose a reason for hiding this comment

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

We have a single call to decodeTerm in the Booster.LLVM module, as far as I know. Do I understand correctly that the cache lifetime is confined to decodeTerm'?

Correct, the cache starts empty in runDecodeM called here. We could carry a cache around across calls but then we would have to check for any unpacked term whether or not we already have it in the cache (term comparisons, but not recursive deep terms). This is good enough I think.

Also, the getTerm trmIdx call will return the fully resolved term? The subterms of that term may be present in the store before, but only once?

Yes, the term is already fully resolved in registerTerm before it is stored in the termStore (...hopefully, because otherwise any inconsistent data would sit there as a thunk waiting to explode).

I think I know the answers, just want a confirmation.

Big brain @jberthold !

😊 not my idea originally, the credit goes to Virgil.

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.

Great to see the speedup!

I left some comments/questions, but they are for my understanding only.

Regarding the Kotnrol tests: I'm running them right now on my PR. Perhaps my out-of-date Nix cache allows me to do that? I'll try running the Kontol tests on this PR later.

@@ -501,12 +501,9 @@ llvmSimplify term = do
withTermContext result $
emitEquationTrace t Nothing (Just "LLVM") Nothing $
Success result
toCache LLVM t result
Copy link
Contributor

Choose a reason for hiding this comment

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

We have another cache access in llvmSimplify, that will actually also write to cache too. Should we remove it as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that one should stay, the two here are duplicates. We need to cache the LLVM result for performance.

Copy link
Contributor

@goodlyrottenapple goodlyrottenapple left a comment

Choose a reason for hiding this comment

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

Nice!

@geo2a
Copy link
Contributor

geo2a commented May 22, 2024

I've ran the Kontrol tests, and the speedup is there too:

Test 3863-llvm-term-cache time master-0f3148308 time (3863-llvm-term-cache/master-0f3148308) time
HevmTests.prove_revert 21.96 23.59 0.9309029249682069
BytesTypeTest.test_bytes4(bytes4) 32.0 34.33 0.9321293329449462
BlockParamsTest.testWarp(uint256)-trace_options2 30.22 32.4 0.932716049382716
ExpectRevertTest.testFail_ExpectRevert_failAndSuccess() 108.37 115.57 0.9377000951804102
AssertTest.testFail_assert_true() 49.13 51.79 0.9486387333462059
AssumeTest.test_assume_staticCall(bool) 52.13 54.51 0.9563382865529262
StoreTest.testStoreLoad() 60.28 62.92 0.958041958041958
StoreTest.testGasLoadColdVM() 38.72 40.18 0.9636635141861623
ArithmeticTest.test_max2(uint256,uint256) 67.01 69.38 0.9658402997982128
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_refute_node 18.29 17.52 1.0439497716894977
TOTAL 478.11000000000007 502.19 0.9520500209084213

some noise is introduced by test_foundry_prove.py::test_foundry_refute_node (the last item), which fails on both master and feature branch.

@jberthold
Copy link
Member Author

jberthold commented May 22, 2024

The kontrol tests show a bit of overhead... with the usual uncertainty (parallelism was 2)

EDIT: clearly your results are much better 😏 not sure what went wrong there. I was using the kontrol version from today with K upgraded to 7.0.75

Test 3863-llvm-term-cache time master-0f3148308 time (3863-llvm-term-cache/master-0f3148308) time
MethodDisambiguateTest.test_method_call() 20.51 24.86 0.8250201126307322
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_remove_node 11.5 13.4 0.8582089552238805
AddrTest.test_notBuiltinAddress_symbolic(address) 39.13 45.05 0.8685904550499446
FreshCheatcodes.test_int128() 25.88 28.4 0.9112676056338028
AllowChangesTest.testAllow() 72.12 76.96 0.9371101871101872
BlockParamsTest.testFee(uint256) 24.98 26.27 0.9508945565283594
MockCallTestFoundry.testMockCalldata() 136.71 142.32 0.9605817875210794
StoreTest.testStoreLoad() 35.46 36.75 0.9648979591836735
EmitContractTest.testExpectEmit() 51.06 49.33 1.0350699371579162
BlockParamsTest.testRoll(uint256) 25.89 24.84 1.0422705314009661
SymbolicStorageTest.testEmptyInitialStorage(uint256) 23.81 22.84 1.0424693520140105
AccountParamsTest.test_getNonce_unknownSymbolic(address) 67.26 64.14 1.048643592142189
StartPrankTestMsgSender.test_startprank_msgsender_setup() 72.93 69.41 1.0507131537242473
AssumeTest.test_multi_assume(address,address) 80.58 76.54 1.0527828586360073
ExpectRevertTest.test_ExpectRevert_increasedDepth() 107.7 102.17 1.0541254771459332
AccountParamsTest.testEtchConcrete() 67.78 64.17 1.0562568178276452
FreshCheatcodes.test_address() 37.84 35.71 1.059647157658919
ExpectRevertTest.test_expectRevert_bytes4() 65.1 61.2 1.0637254901960782
StartPrankTestOrigin.test_startprank_origin_setup() 73.56 69.05 1.0653149891383056
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_split_node 459.5 430.79 1.0666450010445925
FreshCheatcodes.test_freshSymbolicWord() 31.71 29.63 1.0701991225109686
ExpectRevertTest.testFail_expectRevert_empty() 29.74 27.76 1.0713256484149856
PlainPrankTest.test_prank_zeroAddress_true() 103.6 96.57 1.0727969348659003
Setup2Test.testFail_setup() 47.63 44.38 1.073231185218567
kontrol/src/tests/integration/test_foundry_prove.py::test_foundry_merge_nodes 28.94 26.96 1.0734421364985163
SymbolicStorageTest.testFail_SymbolicStorage(uint256) 80.78 75.2 1.0742021276595743
SetUpTest.testSetupData() 50.74 47.09 1.0775111488638776
InitCodeTest.test_init() 80.17 74.33 1.078568545674694
MockCallTestFoundry.testMockNested() 268.46 247.37 1.0852569026155152
SymbolicStorageTest.testFail_SymbolicStorage1(uint256) 89.53 82.37 1.086924851280806
ERC20.sol 50.57 46.5 1.0875268817204302
AssumeTest.testFail_assume_true(uint256,uint256) 46.16 42.38 1.0891930155733835
StoreTest.testGasStoreColdVM() 25.47 23.23 1.0964270340077484
AssertTest.test_assert_true() 40.8 36.88 1.106290672451193
PlainPrankTest.test_startPrank_true() 89.51 80.61 1.1104081379481454
AssumeTest.test_assume_staticCall(bool) 35.76 32.09 1.1143658460579617
ExpectRevertTest.testFail_expectRevert_bytes4() 76.11 67.56 1.1265541740674956
ExpectRevertTest.testFail_expectRevert_multipleReverts() 99.87 87.56 1.1405893101873001
AccountParamsTest.testFail_GetNonce_true() 43.49 37.63 1.1557268137124634
AssumeTest.test_assume_true(uint256,uint256) 26.94 23.14 1.164217804667243
StoreTest.testGasLoadColdVM() 27.06 23.17 1.1678895123003883
BlockParamsTest.testChainId(uint256) 30.1 25.76 1.1684782608695652
GasTest.testSetGas() 24.95 21.15 1.1796690307328606
ConstructorTest.run_constructor() 52.46 44.3 1.1841986455981943
Setup2Test.test_setup() 23.2 19.55 1.186700767263427
ConstructorTest.testFail_constructor() 43.24 36.29 1.1915128134472308
AssertTest.prove_assert_true() 22.15 18.46 1.1998916576381364
AssertTest.test_assert_true_branch(uint256) 53.47 43.96 1.2163330300272974
SetUpDeployTest.test_extcodesize() 55.77 45.37 1.2292263610315188
MockCallTestFoundry.testRevertMock() 53.55 43.2 1.2395833333333333
LoopsTest.test_sum_10() 64.03 51.04 1.2545062695924765
StoreTest.testGasLoadWarmUp() 55.63 43.82 1.2695116385212233
BlockParamsTest.testBlockNumber() 19.98 15.72 1.2709923664122138
BlockParamsTest.testCoinBase() 30.68 22.51 1.3629498000888494
LabelTest.testLabel() 30.0 20.97 1.4306151645207439
TOTAL 3431.55 3168.71 1.082948581599452

@jberthold jberthold merged commit 8b7ed67 into master May 22, 2024
7 checks passed
@jberthold jberthold deleted the 3863-llvm-term-cache branch May 22, 2024 12:11
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.

Term cache to avoid storing term duplicates (addresses cache memory footprint)
3 participants