Skip to content

Move unsat cache to Solvers.hs #743

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Move unsat cache to Solvers.hs #743

wants to merge 2 commits into from

Conversation

msooseth
Copy link
Collaborator

@msooseth msooseth commented May 14, 2025

Description

This moves the UNSAT cache to a central location, in Solvers.hs. This means that UNSAT cache is filled and used even during interpretation. Also, one can simply use checkSatWithProps and the UNSAT cache will be there and working.

This also means that the unsat cache is now available to every place that calls checkSatWithProps, which also includes PleaseAskSMT, i.e. it will now work also when deciding on branches.

We now have a queue for the cache, and it is taken as priority before picking up any new task.

Checklist

  • tested locally
  • added automated tests
  • updated the docs
  • updated the changelog

@msooseth msooseth added the enhancement New feature or request label May 14, 2025
@msooseth msooseth force-pushed the move-unsat-cache branch 5 times, most recently from 44ffb79 to b5c76f9 Compare May 16, 2025 14:35
@gustavo-grieco
Copy link
Collaborator

Can checkSatWithProps be used to in verify so we can also benefit from caching?

@msooseth
Copy link
Collaborator Author

Can checkSatWithProps be used to in verify so we can also benefit from caching?

But we are already using it:

        -- Dispatch the remaining branches to the solver to check for violations
        --
        results <- withRunInIO $ \env -> flip mapConcurrently withQueries $ \(query, leaf) -> do
          res <- env $ checkSatWithProps solvers query
          when conf.debug $ putStrLn $ "   SMT result: " <> show (fst res)
          pure (fst res, leaf)

In fact, I made checkSat a sub-function of checkSatWithProps, and so, it cannot even be called anymore.

@msooseth msooseth force-pushed the move-unsat-cache branch from b5c76f9 to 589331a Compare May 19, 2025 08:00
@msooseth msooseth changed the title [DRAFT] Move unsat cache Move unsat cache May 19, 2025
@msooseth msooseth changed the title Move unsat cache Move unsat cache so Solvers.hs May 19, 2025
@msooseth msooseth force-pushed the move-unsat-cache branch 3 times, most recently from 63c8e81 to d5f2d0e Compare May 19, 2025 08:24
@msooseth
Copy link
Collaborator Author

@gustavo-grieco Basically, this PR is meant to replace all that cache in verify and elsewhere, so we have a single, unified cache.

@msooseth msooseth marked this pull request as ready for review May 19, 2025 08:26
@msooseth msooseth force-pushed the move-unsat-cache branch from b84fda4 to e5b383c Compare May 19, 2025 10:27
@msooseth msooseth requested a review from blishko May 20, 2025 10:42
Copy link
Collaborator

@blishko blishko left a comment

Choose a reason for hiding this comment

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

Looks good, though I am wondering: How is this going to work? Wouldn't all SMT queries be put in the queue before the requests to add results to the cache?

Have you checked that this is working as expected?

@msooseth
Copy link
Collaborator Author

Have you checked that this is working as expected?

It certainly needs to be thought through. It does work sometimes for sure, but I agree that this needs more scrutiny. I wanna see how the ordering is done in this queue.

@msooseth
Copy link
Collaborator Author

I actually figured out why FIFO is correct for the equivalence checker! Kinda crazy. So:

So this:

       wrap <- pool numproc
       parMapIO (runOne env wrap) input

Creates e.g. 5 processes. Then immediately run 5 elements from input and block. Then when any has finished, it will pick up one more, and run that one. Etc. It does this in-order of the input. It will run 5 single tasks. Let's say all 5 are UNSAT. Let's say one finishes. It can only finish if it has already put the UNSAT cache element into the queue. Which means that when parMapIO sees that this thread has finished, it will pick up a new element, and put that task into the channel. But the cache task will be earlier in the channel! So the orchestrate will pick that up and deal with that first. Then it will pick up the next element to be processed.

What we need is a cache task channel that is priority, and does not block :)

@msooseth
Copy link
Collaborator Author

This is now ready for re-review. @blishko maybe you can take a peek?

Copy link
Collaborator

@blishko blishko left a comment

Choose a reason for hiding this comment

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

This looks good!

I would still like to play with it a bit before we merge it.

Comment on lines 676 to 682
do
when conf.debug $ liftIO $ putStrLn " Simplifying expression"
let expr = if opts.simp then (Expr.simplify exprInter) else exprInter
when conf.dumpExprs $ T.writeFile "simplified.expr" (formatExpr expr)
when conf.dumpExprs $ T.writeFile "simplified-conc.expr" (formatExpr $ Expr.simplify $ mapExpr Expr.concKeccakOnePass expr)
when conf.dumpExprs $ liftIO $ T.writeFile "simplified.expr" (formatExpr expr)
when conf.dumpExprs $ liftIO $ T.writeFile "simplified-conc.expr" (formatExpr $ Expr.simplify $ mapExpr Expr.concKeccakOnePass expr)
let flattened = flattenExpr expr
when conf.debug $ do
when conf.debug $ liftIO $ do
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems unrelated to the main point of the PR.
To me it seems better to write liftIO once. What is the problem with that?

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's annoying. the checkSatWithProps needs the env. But it's ugly as-is, let me try to clean it up...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK, it should be cleaner now. Can you maybe re-check? It doesn't print the simplifying... line, but that's not that useful anyway I think. Otherwise, it should be cleaner now :)

@msooseth msooseth force-pushed the move-unsat-cache branch from b527ee6 to 1bd6bde Compare May 22, 2025 10:13
Move unsat cache to Solver.hs in a central location

Co-authored-by: Martin Blicha <martin.blicha@ethereum.org>
@msooseth msooseth force-pushed the move-unsat-cache branch from 1bd6bde to a0d3a19 Compare May 22, 2025 10:14
@msooseth msooseth changed the title Move unsat cache so Solvers.hs Move unsat cache to Solvers.hs May 22, 2025
Copy link
Collaborator

@blishko blishko left a comment

Choose a reason for hiding this comment

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

Can you explain why you need STM (TChan) for the cache? Why the basic Chan is not sufficient?

@@ -69,6 +71,9 @@ data MultiSol = MultiSol
-- | A script to be executed, a list of models to be extracted in the case of a sat result, and a channel where the result should be written
data Task = TaskSingle SingleData | TaskMulti MultiData

newtype UnsatCache = UnsatCache [Prop]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be renamed, no? [Prop] is not the unsat cache. It is more like an Entry that should be added to unsat cache.

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 point! Renamed it CacheEntry, I hope that works :)

@msooseth
Copy link
Collaborator Author

Can you explain why you need STM (TChan) for the cache? Why the basic Chan is not sufficient?

Yes! This queue needs to be nonblocking. The Chan queue doesn't have a non-blocking variant. STM.TChan has a non-blocking read operation:

      mx <- liftIO . atomically $ tryReadTChan cacheq
      case mx of
        Just (CacheEntry props)  -> deal with the entry...
        Nothing -> nothing in the queue, continue...

We need a non-blocking read operation, so we can start solving if there is nothing in the channel.

My understanding is that the other way of doing this is to use STM.TVar directly, which is what STM.TChan is using.

@blishko
Copy link
Collaborator

blishko commented May 26, 2025

Right, that makes sense!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants