{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module What4.Protocol.Online
( OnlineSolver(..)
, AnOnlineSolver(..)
, SolverProcess(..)
, ErrorBehavior(..)
, killSolver
, push
, pop
, reset
, inNewFrame
, inNewFrameWithVars
, check
, checkAndGetModel
, checkWithAssumptions
, checkWithAssumptionsAndModel
, getModel
, getUnsatCore
, getUnsatAssumptions
, getSatResult
, checkSatisfiable
, checkSatisfiableWithModel
) where
import Control.Exception
( SomeException(..), catchJust, tryJust, displayException )
import Control.Monad ( unless )
import Control.Monad (void, forM, forM_)
import Control.Monad.Catch ( MonadMask, bracket_, onException )
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Data.Parameterized.Some
import Data.Proxy
import Data.IORef
import Data.Text (Text)
import qualified Data.Text.Lazy as LazyText
import System.Exit
import System.IO
import qualified System.IO.Streams as Streams
import System.Process
(ProcessHandle, terminateProcess, waitForProcess)
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>))
import What4.Expr
import What4.Interface (SolverEvent(..))
import What4.ProblemFeatures
import What4.Protocol.SMTWriter
import What4.SatResult
import What4.Utils.HandleReader
import What4.Utils.Process (filterAsync)
data AnOnlineSolver = forall s. OnlineSolver s => AnOnlineSolver (Proxy s)
class SMTReadWriter solver => OnlineSolver solver where
startSolverProcess :: forall scope st fs.
ProblemFeatures ->
Maybe Handle ->
ExprBuilder scope st fs ->
IO (SolverProcess scope solver)
shutdownSolverProcess :: forall scope.
SolverProcess scope solver ->
IO (ExitCode, LazyText.Text)
data ErrorBehavior
= ImmediateExit
| ContinueOnError
data SolverProcess scope solver = SolverProcess
{ solverConn :: !(WriterConn scope solver)
, solverCleanupCallback :: IO ExitCode
, solverHandle :: !ProcessHandle
, solverStdin :: !(Streams.OutputStream Text)
, solverResponse :: !(Streams.InputStream Text)
, solverErrorBehavior :: !ErrorBehavior
, solverStderr :: !HandleReader
, solverEvalFuns :: !(SMTEvalFunctions solver)
, solverLogFn :: SolverEvent -> IO ()
, solverName :: String
, solverEarlyUnsat :: IORef (Maybe Int)
, solverSupportsResetAssertions :: Bool
}
killSolver :: SolverProcess t solver -> IO ()
killSolver p =
do catchJust filterAsync
(terminateProcess (solverHandle p))
(\(ex :: SomeException) -> hPutStrLn stderr $ displayException ex)
void $ waitForProcess (solverHandle p)
checkSatisfiable ::
SMTReadWriter solver =>
SolverProcess scope solver ->
String ->
BoolExpr scope ->
IO (SatResult () ())
checkSatisfiable proc rsn p =
readIORef (solverEarlyUnsat proc) >>= \case
Just _ -> return (Unsat ())
Nothing ->
let conn = solverConn proc in
inNewFrame proc $
do assume conn p
check proc rsn
checkSatisfiableWithModel ::
SMTReadWriter solver =>
SolverProcess scope solver ->
String ->
BoolExpr scope ->
(SatResult (GroundEvalFn scope) () -> IO a) ->
IO a
checkSatisfiableWithModel proc rsn p k =
readIORef (solverEarlyUnsat proc) >>= \case
Just _ -> k (Unsat ())
Nothing ->
let conn = solverConn proc in
inNewFrame proc $
do assume conn p
checkAndGetModel proc rsn >>= k
reset :: SMTReadWriter solver => SolverProcess scope solver -> IO ()
reset p =
do let c = solverConn p
n <- popEntryStackToTop c
writeIORef (solverEarlyUnsat p) Nothing
if solverSupportsResetAssertions p then
addCommand c (resetCommand c)
else
do mapM_ (addCommand c) (popManyCommands c n)
addCommand c (pushCommand c)
push :: SMTReadWriter solver => SolverProcess scope solver -> IO ()
push p =
readIORef (solverEarlyUnsat p) >>= \case
Nothing -> do let c = solverConn p
pushEntryStack c
addCommand c (pushCommand c)
Just i -> writeIORef (solverEarlyUnsat p) $! (Just $! i+1)
pop :: SMTReadWriter solver => SolverProcess scope solver -> IO ()
pop p =
readIORef (solverEarlyUnsat p) >>= \case
Nothing -> do let c = solverConn p
popEntryStack c
addCommand c (popCommand c)
Just i
| i <= 1 -> do let c = solverConn p
popEntryStack c
writeIORef (solverEarlyUnsat p) Nothing
addCommand c (popCommand c)
| otherwise -> writeIORef (solverEarlyUnsat p) $! (Just $! i-1)
popStackOnly :: SMTReadWriter solver => SolverProcess scope solver -> IO ()
popStackOnly p =
readIORef (solverEarlyUnsat p) >>= \case
Nothing -> do let c = solverConn p
popEntryStack c
Just i
| i <= 1 -> do let c = solverConn p
popEntryStack c
writeIORef (solverEarlyUnsat p) Nothing
| otherwise -> writeIORef (solverEarlyUnsat p) $! (Just $! i-1)
inNewFrame :: (MonadIO m, MonadMask m, SMTReadWriter solver) => SolverProcess scope solver -> m a -> m a
inNewFrame p action = inNewFrameWithVars p [] action
inNewFrameWithVars :: (MonadIO m, MonadMask m, SMTReadWriter solver)
=> SolverProcess scope solver
-> [Some (ExprBoundVar scope)]
-> m a
-> m a
inNewFrameWithVars p vars action =
case solverErrorBehavior p of
ContinueOnError ->
bracket_ (liftIO $ pushWithVars)
(liftIO $ pop p)
action
ImmediateExit ->
do liftIO $ pushWithVars
x <- (onException action (liftIO $ popStackOnly p))
liftIO $ pop p
return x
where
conn = solverConn p
pushWithVars = do
push p
forM_ vars (\(Some bv) -> bindVarAsFree conn bv)
checkWithAssumptions ::
SMTReadWriter solver =>
SolverProcess scope solver ->
String ->
[BoolExpr scope] ->
IO ([Text], SatResult () ())
checkWithAssumptions proc rsn ps =
do let conn = solverConn proc
readIORef (solverEarlyUnsat proc) >>= \case
Just _ -> return ([], Unsat ())
Nothing ->
do tms <- forM ps (mkFormula conn)
nms <- forM tms (freshBoundVarName conn EqualityDefinition [] BoolTypeMap)
solverLogFn proc
SolverStartSATQuery
{ satQuerySolverName = solverName proc
, satQueryReason = rsn
}
addCommands conn (checkWithAssumptionsCommands conn nms)
sat_result <- getSatResult proc
solverLogFn proc
SolverEndSATQuery
{ satQueryResult = sat_result
, satQueryError = Nothing
}
return (nms, sat_result)
checkWithAssumptionsAndModel ::
SMTReadWriter solver =>
SolverProcess scope solver ->
String ->
[BoolExpr scope] ->
IO (SatResult (GroundEvalFn scope) ())
checkWithAssumptionsAndModel proc rsn ps =
do (_nms, sat_result) <- checkWithAssumptions proc rsn ps
case sat_result of
Unknown -> return Unknown
Unsat x -> return (Unsat x)
Sat{} -> Sat <$> getModel proc
check :: SMTReadWriter solver => SolverProcess scope solver -> String -> IO (SatResult () ())
check p rsn =
readIORef (solverEarlyUnsat p) >>= \case
Just _ -> return (Unsat ())
Nothing ->
do let c = solverConn p
solverLogFn p
SolverStartSATQuery
{ satQuerySolverName = solverName p
, satQueryReason = rsn
}
addCommands c (checkCommands c)
sat_result <- getSatResult p
solverLogFn p
SolverEndSATQuery
{ satQueryResult = sat_result
, satQueryError = Nothing
}
return sat_result
checkAndGetModel :: SMTReadWriter solver => SolverProcess scope solver -> String -> IO (SatResult (GroundEvalFn scope) ())
checkAndGetModel yp rsn = do
sat_result <- check yp rsn
case sat_result of
Unsat x -> return $! Unsat x
Unknown -> return Unknown
Sat () -> Sat <$> getModel yp
getModel :: SMTReadWriter solver => SolverProcess scope solver -> IO (GroundEvalFn scope)
getModel p = smtExprGroundEvalFn (solverConn p)
$ smtEvalFuns (solverConn p) (solverResponse p)
getUnsatAssumptions :: SMTReadWriter solver => SolverProcess scope solver -> IO [(Bool,Text)]
getUnsatAssumptions proc =
do let conn = solverConn proc
unless (supportedFeatures conn `hasProblemFeature` useUnsatAssumptions) $
fail $ show $ text (smtWriterName conn) <+> text "is not configured to produce UNSAT assumption lists"
addCommandNoAck conn (getUnsatAssumptionsCommand conn)
smtUnsatAssumptionsResult conn (solverResponse proc)
getUnsatCore :: SMTReadWriter solver => SolverProcess scope solver -> IO [Text]
getUnsatCore proc =
do let conn = solverConn proc
unless (supportedFeatures conn `hasProblemFeature` useUnsatCores) $
fail $ show $ text (smtWriterName conn) <+> text "is not configured to produce UNSAT cores"
addCommandNoAck conn (getUnsatCoreCommand conn)
smtUnsatCoreResult conn (solverResponse proc)
getSatResult :: SMTReadWriter s => SolverProcess t s -> IO (SatResult () ())
getSatResult yp = do
let ph = solverHandle yp
let err_reader = solverStderr yp
sat_result <- tryJust filterAsync (smtSatResult yp (solverResponse yp))
case sat_result of
Right ok -> return ok
Left (SomeException e) ->
do
terminateProcess ph
txt <- readAllLines err_reader
ec <- waitForProcess ph
let ec_code = case ec of
ExitSuccess -> 0
ExitFailure code -> code
fail $ unlines
[ "The solver terminated with exit code "++
show ec_code ++ ".\n"
, "*** exception: " ++ displayException e
, "*** standard error:"
, LazyText.unpack txt
]