backport prover command interface

Conflicts:
	src/Cryptol/REPL/Command.hs
	src/Cryptol/Symbolic.hs
This commit is contained in:
Adam C. Foltzer 2015-07-10 18:19:53 -07:00
parent 917cc27145
commit 579ccc96a0
4 changed files with 196 additions and 133 deletions

View File

@ -40,7 +40,8 @@ flag self-contained
library
Default-language:
Haskell98
Build-depends: base >= 4.6 && < 5,
Build-depends: base >= 4.7 && < 5,
base-compat >= 0.6,
array >= 0.4,
async >= 2.0,
containers >= 0.5,
@ -56,7 +57,7 @@ library
process >= 1.2,
QuickCheck >= 2.7,
random >= 1.0.1,
sbv (>= 4.3 && < 5.0) || (>= 5.1 && < 5.2),
sbv >= 5.3 && < 5.4,
smtLib >= 1.0.7,
syb >= 0.4,
text >= 1.1,

View File

@ -54,6 +54,7 @@ import Cryptol.Utils.Panic(panic)
import qualified Cryptol.Parser.AST as P
import Cryptol.Prims.Doc(helpDoc)
import qualified Cryptol.Transform.Specialize as S
import Cryptol.Symbolic (ProverCommand(..), QueryType(..), SatNum(..))
import qualified Cryptol.Symbolic as Symbolic
import Control.DeepSeq
@ -336,9 +337,10 @@ satCmd, proveCmd :: String -> REPL ()
satCmd = cmdProveSat True
proveCmd = cmdProveSat False
-- | Run a SAT solver on the given expression. Binds the @it@ variable
-- to a record whose form depends on the expression given. See ticket
-- #66 for a discussion of this design.
-- | Console-specific version of 'proveSat'. Prints output to the
-- console, and binds the @it@ variable to a record whose form depends
-- on the expression given. See ticket #66 for a discussion of this
-- design.
cmdProveSat :: Bool -> String -> REPL ()
cmdProveSat isSat "" =
do xs <- getPropertyNames
@ -349,81 +351,104 @@ cmdProveSat isSat "" =
then rPutStr $ ":sat " ++ x ++ "\n\t"
else rPutStr $ ":prove " ++ x ++ "\n\t"
cmdProveSat isSat x
cmdProveSat isSat str = do
cmdProveSat isSat expr = do
let cexStr | isSat = "satisfying assignment"
| otherwise = "counterexample"
EnvString proverName <- getUser "prover"
EnvString fileName <- getUser "smtfile"
let mfile = if fileName == "-" then Nothing else Just fileName
case proverName of
"offline" -> offlineProveSat isSat str mfile
_ -> onlineProveSat isSat str proverName mfile
"offline" -> do
result <- offlineProveSat isSat expr mfile
case result of
Left msg -> rPutStrLn msg
Right smtlib -> do
let filename = fromMaybe "standard output" mfile
let satWord | isSat = "satisfiability"
| otherwise = "validity"
rPutStrLn $
"Writing to SMT-Lib file " ++ filename ++ "..."
rPutStrLn $
"To determine the " ++ satWord ++
" of the expression, use an external SMT solver."
case mfile of
Just path -> io $ writeFile path smtlib
Nothing -> rPutStr smtlib
_ -> do
result <- onlineProveSat isSat expr proverName mfile
ppOpts <- getPPValOpts
case result of
Symbolic.EmptyResult ->
panic "REPL.Command" [ "got EmptyResult for online prover query" ]
Symbolic.ProverError msg -> rPutStrLn msg
Symbolic.ThmResult ts -> do
rPutStrLn (if isSat then "Unsatisfiable" else "Q.E.D.")
let (t, e) = mkSolverResult cexStr (not isSat) (Left ts)
bindItVariable t e
Symbolic.AllSatResult tevss -> do
let tess = map (map $ \(t,e,_) -> (t,e)) tevss
vss = map (map $ \(_,_,v) -> v) tevss
ppvs vs = do
parseExpr <- replParseExpr expr
let docs = map (pp . E.WithBase ppOpts) vs
-- function application has precedence 3
doc = ppPrec 3 parseExpr
rPrint $ hsep (doc : docs) <+>
text (if isSat then "= True" else "= False")
resultRecs = map (mkSolverResult cexStr isSat . Right) tess
collectTes tes = (t, es)
where
(ts, es) = unzip tes
t = case nub ts of
[t'] -> t'
_ -> panic "REPL.Command.onlineProveSat"
[ "satisfying assignments with different types" ]
(ty, exprs) =
case resultRecs of
[] -> panic "REPL.Command.onlineProveSat"
[ "no satisfying assignments after mkSovlerResult" ]
[(t, e)] -> (t, [e])
_ -> collectTes resultRecs
forM_ vss ppvs
case (ty, exprs) of
(t, [e]) -> bindItVariable t e
(t, es ) -> bindItVariables t es
onlineProveSat :: Bool
-> String -> String -> Maybe FilePath -> REPL ()
-> String -> String -> Maybe FilePath -> REPL Symbolic.ProverResult
onlineProveSat isSat str proverName mfile = do
EnvBool verbose <- getUser "debug"
mSatNum <- getUserSatNum
let cexStr | isSat = "satisfying assignment"
| otherwise = "counterexample"
satNum <- getUserSatNum
parseExpr <- replParseExpr str
(expr, schema) <- replCheckExpr parseExpr
denv <- getDynEnv
result <- liftModuleCmd $
Symbolic.satProve
isSat
mSatNum
(proverName, verbose)
(M.deDecls denv)
mfile
(expr, schema)
ppOpts <- getPPValOpts
case result of
Symbolic.EmptyResult ->
panic "REPL.Command" [ "got EmptyResult for online prover query" ]
Symbolic.ProverError msg -> rPutStrLn msg
Symbolic.ThmResult ts -> do
rPutStrLn (if isSat then "Unsatisfiable" else "Q.E.D.")
let (t, e) = mkSolverResult cexStr (not isSat) (Left ts)
bindItVariable t e
Symbolic.AllSatResult tevss -> do
let tess = map (map $ \(t,e,_) -> (t,e)) tevss
vss = map (map $ \(_,_,v) -> v) tevss
ppvs vs = do
let docs = map (pp . E.WithBase ppOpts) vs
-- function application has precedence 3
doc = ppPrec 3 parseExpr
rPrint $ hsep (doc : docs) <+>
text (if isSat then "= True" else "= False")
resultRecs = map (mkSolverResult cexStr isSat . Right) tess
collectTes tes = (t, es)
where
(ts, es) = unzip tes
t = case nub ts of
[t'] -> t'
_ -> panic "REPL.Command.onlineProveSat"
[ "satisfying assignments with different types" ]
(ty, exprs) =
case resultRecs of
[] -> panic "REPL.Command.onlineProveSat"
[ "no satisfying assignments after mkSovlerResult" ]
[(t, e)] -> (t, [e])
_ -> collectTes resultRecs
forM_ vss ppvs
case (ty, exprs) of
(t, [e]) -> bindItVariable t e
(t, es ) -> bindItVariables t es
offlineProveSat :: Bool -> String -> Maybe FilePath -> REPL ()
offlineProveSat isSat str mfile = do
EnvBool vrb <- getUser "debug"
parseExpr <- replParseExpr str
exsch <- replCheckExpr parseExpr
decls <- fmap M.deDecls getDynEnv
result <- liftModuleCmd $
Symbolic.satProveOffline isSat vrb decls mfile exsch
case result of
Symbolic.ProverError msg -> rPutStrLn msg
Symbolic.EmptyResult -> return ()
_ -> panic "REPL.Command" [ "unexpected prover result for offline prover" ]
let cmd = Symbolic.ProverCommand {
pcQueryType = if isSat then SatQuery satNum else ProveQuery
, pcProverName = proverName
, pcVerbose = verbose
, pcExtraDecls = decls
, pcSmtFile = mfile
, pcExpr = expr
, pcSchema = schema
}
liftModuleCmd $ Symbolic.satProve cmd
offlineProveSat :: Bool -> String -> Maybe FilePath -> REPL (Either String String)
offlineProveSat isSat str mfile = do
EnvBool verbose <- getUser "debug"
parseExpr <- replParseExpr str
(expr, schema) <- replCheckExpr parseExpr
decls <- fmap M.deDecls getDynEnv
let cmd = Symbolic.ProverCommand {
pcQueryType = if isSat then SatQuery (SomeSat 0) else ProveQuery
, pcProverName = "offline"
, pcVerbose = verbose
, pcExtraDecls = decls
, pcSmtFile = mfile
, pcExpr = expr
, pcSchema = schema
}
liftModuleCmd $ Symbolic.satProveOffline cmd
-- | Make a type/expression pair that is suitable for binding to @it@
-- after running @:sat@ or @:prove@

View File

@ -80,7 +80,7 @@ import qualified Cryptol.TypeCheck.AST as T
import Cryptol.Utils.PP
import Cryptol.Utils.Panic (panic)
import qualified Cryptol.Parser.AST as P
import Cryptol.Symbolic (proverNames, lookupProver)
import Cryptol.Symbolic (proverNames, lookupProver, SatNum(..))
import Control.Monad (ap,unless,when)
import Data.IORef
@ -582,12 +582,12 @@ checkSatNum val = case val of
_ -> return $ Just "must be an integer > 0 or \"all\""
_ -> return $ Just "unable to parse a value for satNum"
getUserSatNum :: REPL (Maybe Int)
getUserSatNum :: REPL SatNum
getUserSatNum = do
EnvString s <- getUser "satNum"
case s of
"all" -> return Nothing
_ | Just n <- readMaybe s -> return (Just n)
"all" -> return AllSat
_ | Just n <- readMaybe s -> return (SomeSat n)
_ -> panic "REPL.Monad.getUserSatNum"
[ "invalid satNum option" ]

View File

@ -6,23 +6,24 @@
-- Stability : provisional
-- Portability : portable
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
module Cryptol.Symbolic where
import Control.Monad (replicateM, when, zipWithM)
import Control.Monad.IO.Class
import Data.List (transpose, intercalate)
import qualified Data.Map as Map
import Data.Maybe
import qualified Control.Exception as X
import qualified Data.SBV.Dynamic as SBV
import qualified Cryptol.ModuleSystem as M
import qualified Cryptol.ModuleSystem.Env as M
import qualified Cryptol.ModuleSystem.Monad as M
import Cryptol.Symbolic.Prims
import Cryptol.Symbolic.Value
@ -75,6 +76,30 @@ lookupProver s =
type SatResult = [(Type, Expr, Eval.Value)]
data SatNum = AllSat | SomeSat Int
deriving (Show)
data QueryType = SatQuery SatNum | ProveQuery
deriving (Show)
data ProverCommand = ProverCommand {
pcQueryType :: QueryType
-- ^ The type of query to run
, pcProverName :: String
-- ^ Which prover to use (one of the strings in 'proverConfigs')
, pcVerbose :: Bool
-- ^ Verbosity flag passed to SBV
, pcExtraDecls :: [DeclGroup]
-- ^ Extra declarations to bring into scope for symbolic
-- simulation
, pcSmtFile :: Maybe FilePath
-- ^ Optionally output the SMTLIB query to a file
, pcExpr :: Expr
-- ^ The typechecked expression to evaluate
, pcSchema :: Schema
-- ^ The 'Schema' of @pcExpr@
}
-- | A prover result is either an error message, an empty result (eg
-- for the offline prover), a counterexample or a lazy list of
-- satisfying assignments.
@ -83,42 +108,65 @@ data ProverResult = AllSatResult [SatResult] -- LAZY
| EmptyResult
| ProverError String
satSMTResults :: SBV.SatResult -> [SBV.SMTResult]
satSMTResults (SBV.SatResult r) = [r]
allSatSMTResults :: SBV.AllSatResult -> [SBV.SMTResult]
allSatSMTResults (SBV.AllSatResult (_, rs)) = rs
thmSMTResults :: SBV.ThmResult -> [SBV.SMTResult]
thmSMTResults (SBV.ThmResult r) = [r]
satProve :: Bool
-> Maybe Int -- ^ satNum
-> (String, Bool)
-> [DeclGroup]
-> Maybe FilePath
-> (Expr, Schema)
-> M.ModuleCmd ProverResult
satProve isSat mSatNum (proverName, verbose) edecls mfile (expr, schema) = protectStack $ \modEnv -> do
let extDgs = allDeclGroups modEnv ++ edecls
proverError :: String -> M.ModuleCmd ProverResult
proverError msg modEnv = return (Right (ProverError msg, modEnv), [])
satProve :: ProverCommand -> M.ModuleCmd ProverResult
satProve ProverCommand {..} = protectStack proverError $ \modEnv ->
M.runModuleM modEnv $ do
let (isSat, mSatNum) = case pcQueryType of
ProveQuery -> (False, Nothing)
SatQuery sn -> case sn of
SomeSat n -> (True, Just n)
AllSat -> (True, Nothing)
let extDgs = allDeclGroups modEnv ++ pcExtraDecls
provers <-
case proverName of
"any" -> SBV.sbvAvailableSolvers
_ -> return [(lookupProver proverName) { SBV.smtFile = mfile }]
let provers' = [ p { SBV.timing = verbose, SBV.verbose = verbose } | p <- provers ]
case pcProverName of
"any" -> M.io SBV.sbvAvailableSolvers
_ -> return [(lookupProver pcProverName) { SBV.smtFile = pcSmtFile }]
let provers' = [ p { SBV.timing = pcVerbose, SBV.verbose = pcVerbose } | p <- provers ]
let tyFn = if isSat then existsFinType else forallFinType
let runProver fn tag e = do
when verbose $ liftIO $
case provers of
[prover] -> do
when pcVerbose $ M.io $
putStrLn $ "Trying proof with " ++ show prover
res <- M.io (fn prover e)
when pcVerbose $ M.io $
putStrLn $ "Got result from " ++ show prover
return (tag res)
_ ->
return [ SBV.ProofError
prover
[":sat with option prover=any requires option satNum=1"]
| prover <- provers ]
runProvers fn tag e = do
when pcVerbose $ M.io $
putStrLn $ "Trying proof with " ++
intercalate ", " (map show provers)
(firstProver, res) <- fn provers' e
when verbose $ liftIO $
(firstProver, res) <- M.io $ fn provers' e
when pcVerbose $ M.io $
putStrLn $ "Got result from " ++ show firstProver
return (tag res)
let runFn | isSat = runProver SBV.allSatWithAny allSatSMTResults
| otherwise = runProver SBV.proveWithAny thmSMTResults
case predArgTypes schema of
Left msg -> return (Right (ProverError msg, modEnv), [])
Right ts -> do when verbose $ putStrLn "Simulating..."
let env = evalDecls emptyEnv extDgs
let v = evalExpr env expr
let runFn = case pcQueryType of
ProveQuery -> runProvers SBV.proveWithAny thmSMTResults
SatQuery sn -> case sn of
SomeSat 1 -> runProvers SBV.satWithAny satSMTResults
_ -> runProver SBV.allSatWith allSatSMTResults
case predArgTypes pcSchema of
Left msg -> return (ProverError msg)
Right ts -> do when pcVerbose $ M.io $ putStrLn "Simulating..."
let env = evalDecls mempty extDgs
let v = evalExpr env pcExpr
results' <- runFn $ do
args <- mapM tyFn ts
b <- return $! fromVBit (foldl fromVFun v args)
@ -152,48 +200,37 @@ satProve isSat mSatNum (proverName, verbose) edecls mfile (expr, schema) = prote
| otherwise = show . SBV.ThmResult . head
boom = panic "Cryptol.Symbolic.sat"
[ "attempted to evaluate bogus boolean for pretty-printing" ]
return (Right (esatexprs, modEnv), [])
return esatexprs
satProveOffline :: Bool
-> Bool
-> [DeclGroup]
-> Maybe FilePath
-> (Expr, Schema)
-> M.ModuleCmd ProverResult
satProveOffline isSat vrb edecls mfile (expr, schema) =
protectStack $ \modEnv -> do
let extDgs = allDeclGroups modEnv ++ edecls
satProveOffline :: ProverCommand -> M.ModuleCmd (Either String String)
satProveOffline ProverCommand {..} =
protectStack (\msg modEnv -> return (Right (Left msg, modEnv), [])) $ \modEnv -> do
let isSat = case pcQueryType of
ProveQuery -> False
SatQuery _ -> True
let extDgs = allDeclGroups modEnv ++ pcExtraDecls
let tyFn = if isSat then existsFinType else forallFinType
let filename = fromMaybe "standard output" mfile
case predArgTypes schema of
Left msg -> return (Right (ProverError msg, modEnv), [])
case predArgTypes pcSchema of
Left msg -> return (Right (Left msg, modEnv), [])
Right ts ->
do when vrb $ putStrLn "Simulating..."
let env = evalDecls emptyEnv extDgs
let v = evalExpr env expr
let satWord | isSat = "satisfiability"
| otherwise = "validity"
txt <- SBV.compileToSMTLib smtMode isSat $ do
args <- mapM tyFn ts
b <- return $! fromVBit (foldl fromVFun v args)
liftIO $ putStrLn $
"Writing to SMT-Lib file " ++ filename ++ "..."
return b
liftIO $ putStrLn $
"To determine the " ++ satWord ++
" of the expression, use an external SMT solver."
case mfile of
Just path -> writeFile path txt
Nothing -> putStr txt
return (Right (EmptyResult, modEnv), [])
do when pcVerbose $ putStrLn "Simulating..."
let env = evalDecls mempty extDgs
let v = evalExpr env pcExpr
smtlib <- SBV.compileToSMTLib SBV.SMTLib2 isSat $ do
args <- mapM tyFn ts
b <- return $! fromVBit (foldl fromVFun v args)
return b
return (Right (Right smtlib, modEnv), [])
protectStack :: M.ModuleCmd ProverResult
-> M.ModuleCmd ProverResult
protectStack cmd modEnv = X.catchJust isOverflow (cmd modEnv) handler
protectStack :: (String -> M.ModuleCmd a)
-> M.ModuleCmd a
-> M.ModuleCmd a
protectStack mkErr cmd modEnv =
X.catchJust isOverflow (cmd modEnv) handler
where isOverflow X.StackOverflow = Just ()
isOverflow _ = Nothing
msg = "Symbolic evaluation failed to terminate."
handler () = return (Right (ProverError msg, modEnv), [])
handler () = mkErr msg modEnv
parseValues :: [FinType] -> [SBV.CW] -> ([Eval.Value], [SBV.CW])
parseValues [] cws = ([], cws)