------------------------------------------------------------------------ -- | -- Module : What4.Solver.CVC5 -- Description : Solver adapter code for cvc5 -- Copyright : (c) Galois, Inc 2022 -- License : BSD3 -- Maintainer : Rob Dockins -- Stability : provisional -- -- CVC5-specific tweaks to the basic SMTLib2 solver interface. ------------------------------------------------------------------------ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module What4.Solver.CVC5 ( CVC5(..) , cvc5Features , cvc5Adapter , cvc5Path , cvc5Timeout , cvc5Options , runCVC5InOverride , withCVC5 , writeCVC5SMT2File , writeMultiAsmpCVC5SMT2File , runCVC5SyGuS , withCVC5_SyGuS , writeCVC5SyFile ) where import Control.Monad (forM_, when) import Data.Bits import Data.String import System.IO import qualified System.IO.Streams as Streams import Data.Parameterized.Map (MapF) import Data.Parameterized.Some import What4.BaseTypes import What4.Concrete import What4.Config import What4.Expr.Builder import What4.Expr.GroundEval import What4.Interface import What4.ProblemFeatures import What4.Protocol.Online import qualified What4.Protocol.SMTLib2 as SMT2 import What4.Protocol.SMTLib2.Response ( strictSMTParseOpt ) import qualified What4.Protocol.SMTLib2.Response as RSP import qualified What4.Protocol.SMTLib2.Syntax as Syntax import What4.Protocol.SMTWriter import What4.SatResult import What4.Solver.Adapter import What4.Utils.Process intWithRangeOpt :: ConfigOption BaseIntegerType -> Integer -> Integer -> ConfigDesc intWithRangeOpt nm lo hi = mkOpt nm sty Nothing Nothing where sty = integerWithRangeOptSty (Inclusive lo) (Inclusive hi) data CVC5 = CVC5 deriving Show -- | Path to cvc5 cvc5Path :: ConfigOption (BaseStringType Unicode) cvc5Path = configOption knownRepr "solver.cvc5.path" cvc5RandomSeed :: ConfigOption BaseIntegerType cvc5RandomSeed = configOption knownRepr "solver.cvc5.random-seed" -- | Per-check timeout, in milliseconds (zero is none) cvc5Timeout :: ConfigOption BaseIntegerType cvc5Timeout = configOption knownRepr "solver.cvc5.timeout" -- | Control strict parsing for cvc5 solver responses (defaults -- to solver.strict-parsing option setting). cvc5StrictParsing :: ConfigOption BaseBoolType cvc5StrictParsing = configOption knownRepr "solver.cvc5.strict_parsing" cvc5Options :: [ConfigDesc] cvc5Options = let pathOpt co = mkOpt co executablePathOptSty (Just "Path to CVC5 executable") (Just (ConcreteString "cvc5")) p1 = pathOpt cvc5Path r1 = intWithRangeOpt cvc5RandomSeed (negate (2^(30::Int)-1)) (2^(30::Int)-1) tmOpt co = mkOpt co integerOptSty (Just "Per-check timeout in milliseconds (zero is none)") (Just (ConcreteInteger 0)) t1 = tmOpt cvc5Timeout in [ p1, r1, t1 , copyOpt (const $ configOptionText cvc5StrictParsing) strictSMTParseOpt ] <> SMT2.smtlib2Options cvc5Adapter :: SolverAdapter st cvc5Adapter = SolverAdapter { solver_adapter_name = "cvc5" , solver_adapter_config_options = cvc5Options , solver_adapter_check_sat = runCVC5InOverride , solver_adapter_write_smt2 = writeCVC5SMT2File } indexType :: [SMT2.Sort] -> SMT2.Sort indexType [i] = i indexType il = SMT2.smtlib2StructSort @CVC5 il indexCtor :: [SMT2.Term] -> SMT2.Term indexCtor [i] = i indexCtor il = SMT2.smtlib2StructCtor @CVC5 il instance SMT2.SMTLib2Tweaks CVC5 where smtlib2tweaks = CVC5 smtlib2arrayType il r = SMT2.arraySort (indexType il) r smtlib2arrayConstant = Just $ \idx rtp v -> SMT2.arrayConst (indexType idx) rtp v smtlib2arraySelect a i = SMT2.arraySelect a (indexCtor i) smtlib2arrayUpdate a i = SMT2.arrayStore a (indexCtor i) smtlib2declareStructCmd _ = Nothing smtlib2StructSort [] = Syntax.varSort "Tuple" smtlib2StructSort tps = Syntax.Sort $ "(Tuple" <> foldMap f tps <> ")" where f x = " " <> Syntax.unSort x smtlib2StructCtor args = Syntax.term_app "mkTuple" args smtlib2StructProj _n i x = Syntax.term_app (Syntax.builder_list ["_", "tupSel", fromString (show i)]) [ x ] cvc5Features :: ProblemFeatures cvc5Features = useComputableReals .|. useIntegerArithmetic .|. useSymbolicArrays .|. useStrings .|. useStructs .|. useFloatingPoint .|. useUnsatCores .|. useUnsatAssumptions .|. useUninterpFunctions .|. useDefinedFunctions .|. useBitvectors .|. useQuantifiers .|. useProduceAbducts writeMultiAsmpCVC5SMT2File :: ExprBuilder t st fs -> Handle -> [BoolExpr t] -> IO () writeMultiAsmpCVC5SMT2File sym h ps = do bindings <- getSymbolVarBimap sym out_str <- Streams.encodeUtf8 =<< Streams.handleToOutputStream h in_str <- Streams.nullInput let cfg = getConfiguration sym strictness <- maybe Strict (\c -> if fromConcreteBool c then Strict else Lenient) <$> (getOption =<< getOptionSetting RSP.strictSMTParsing cfg) c <- SMT2.newWriter CVC5 out_str in_str nullAcknowledgementAction strictness "CVC5" True cvc5Features True bindings SMT2.setLogic c Syntax.allLogic SMT2.setProduceModels c True forM_ ps $ SMT2.assume c SMT2.writeCheckSat c SMT2.writeExit c writeCVC5SMT2File :: ExprBuilder t st fs -> Handle -> [BoolExpr t] -> IO () writeCVC5SMT2File sym h ps = writeMultiAsmpCVC5SMT2File sym h ps instance SMT2.SMTLib2GenericSolver CVC5 where defaultSolverPath _ = findSolverPath cvc5Path . getConfiguration defaultSolverArgs _ sym = do let cfg = getConfiguration sym timeout <- getOption =<< getOptionSetting cvc5Timeout cfg let extraOpts = case timeout of Just (ConcreteInteger n) | n > 0 -> ["--tlimit-per=" ++ show n] _ -> [] return $ ["--lang", "smt2", "--incremental", "--strings-exp", "--fp-exp"] ++ extraOpts getErrorBehavior _ = SMT2.queryErrorBehavior defaultFeatures _ = cvc5Features supportsResetAssertions _ = True setDefaultLogicAndOptions writer = do -- Tell cvc5 to use all supported logics. SMT2.setLogic writer Syntax.allLogic -- Tell cvc5 to produce models SMT2.setProduceModels writer True -- Tell cvc5 to produce abducts SMT2.setOption writer "produce-abducts" "true" runCVC5InOverride :: ExprBuilder t st fs -> LogData -> [BoolExpr t] -> (SatResult (GroundEvalFn t, Maybe (ExprRangeBindings t)) () -> IO a) -> IO a runCVC5InOverride = SMT2.runSolverInOverride CVC5 nullAcknowledgementAction (SMT2.defaultFeatures CVC5) (Just cvc5StrictParsing) -- | Run cvc5 in a session. cvc5 will be configured to produce models, but -- otherwise left with the default configuration. withCVC5 :: ExprBuilder t st fs -> FilePath -- ^ Path to cvc5 executable -> LogData -> (SMT2.Session t CVC5 -> IO a) -- ^ Action to run -> IO a withCVC5 = SMT2.withSolver CVC5 nullAcknowledgementAction (SMT2.defaultFeatures CVC5) (Just cvc5StrictParsing) setInteractiveLogicAndOptions :: SMT2.SMTLib2Tweaks a => WriterConn t (SMT2.Writer a) -> IO () setInteractiveLogicAndOptions writer = do -- Tell cvc5 to acknowledge successful commands SMT2.setOption writer "print-success" "true" -- Tell cvc5 to produce models SMT2.setOption writer "produce-models" "true" -- Tell cvc5 to make declarations global, so they are not removed by 'pop' commands SMT2.setOption writer "global-declarations" "true" -- Tell cvc5 to compute UNSAT cores, if that feature is enabled when (supportedFeatures writer `hasProblemFeature` useUnsatCores) $ do SMT2.setOption writer "produce-unsat-cores" "true" -- Tell cvc5 to produce abducts, if that feature is enabled when (supportedFeatures writer `hasProblemFeature` useProduceAbducts) $ do SMT2.setOption writer "produce-abducts" "true" -- Tell cvc5 to use all supported logics. SMT2.setLogic writer Syntax.allLogic instance OnlineSolver (SMT2.Writer CVC5) where startSolverProcess feat mbIOh sym = do timeout <- SolverGoalTimeout <$> (getOpt =<< getOptionSetting cvc5Timeout (getConfiguration sym)) SMT2.startSolver CVC5 SMT2.smtAckResult setInteractiveLogicAndOptions timeout feat (Just cvc5StrictParsing) mbIOh sym shutdownSolverProcess = SMT2.shutdownSolver CVC5 -- | `CVC5_SyGuS` implements a `SMT2.SMTLib2GenericSolver` instance that is -- different from `CVC5` in that it provides SyGuS specific implementations for -- `defaultSolverArgs` and `setDefaultLogicAndOptions`. data CVC5_SyGuS = CVC5_SyGuS deriving Show instance SMT2.SMTLib2Tweaks CVC5_SyGuS where smtlib2tweaks = CVC5_SyGuS smtlib2arrayType = SMT2.smtlib2arrayType @CVC5 smtlib2arrayConstant = SMT2.smtlib2arrayConstant @CVC5 smtlib2arraySelect = SMT2.smtlib2arraySelect @CVC5 smtlib2arrayUpdate = SMT2.smtlib2arrayUpdate @CVC5 smtlib2declareStructCmd = SMT2.smtlib2declareStructCmd @CVC5 smtlib2StructSort = SMT2.smtlib2StructSort @CVC5 smtlib2StructCtor = SMT2.smtlib2StructCtor @CVC5 smtlib2StructProj = SMT2.smtlib2StructProj @CVC5 instance SMT2.SMTLib2GenericSolver CVC5_SyGuS where defaultSolverPath _ = SMT2.defaultSolverPath CVC5 defaultSolverArgs _ sym = do let cfg = getConfiguration sym timeout <- getOption =<< getOptionSetting cvc5Timeout cfg let extraOpts = case timeout of Just (ConcreteInteger n) | n > 0 -> ["--tlimit-per=" ++ show n] _ -> [] return $ ["--sygus", "--lang", "sygus2", "--strings-exp", "--fp-exp"] ++ extraOpts getErrorBehavior _ = SMT2.queryErrorBehavior defaultFeatures _ = SMT2.defaultFeatures CVC5 supportsResetAssertions _ = SMT2.supportsResetAssertions CVC5 setDefaultLogicAndOptions writer = do -- Tell cvc5 to use all supported logics. SMT2.setLogic writer Syntax.allLogic -- | Find a solution to a Syntax-Guided Synthesis (SyGuS) problem. -- -- For more information, see the [SyGuS standard](https://sygus.org/). runCVC5SyGuS :: sym ~ ExprBuilder t st fs => sym -> LogData -> [SomeSymFn sym] -> [BoolExpr t] -> IO (SatResult (MapF (SymFnWrapper sym) (SymFnWrapper sym)) ()) runCVC5SyGuS sym log_data synth_fns constraints = do logSolverEvent sym (SolverStartSATQuery $ SolverStartSATQueryRec { satQuerySolverName = show CVC5_SyGuS , satQueryReason = logReason log_data }) path <- SMT2.defaultSolverPath CVC5_SyGuS sym withCVC5_SyGuS sym path (log_data { logVerbosity = 2 }) $ \session -> do writeSyGuSProblem sym (SMT2.sessionWriter session) synth_fns constraints result <- RSP.getLimitedSolverResponse "check-synth" (\case RSP.AckSuccessSExp sexp -> Just $ Sat sexp RSP.AckInfeasible -> Just $ Unsat () RSP.AckFail -> Just Unknown _ -> Nothing) (SMT2.sessionWriter session) Syntax.checkSynth logSolverEvent sym (SolverEndSATQuery $ SolverEndSATQueryRec { satQueryResult = forgetModelAndCore result , satQueryError = Nothing }) traverseSatResult (\sexp -> SMT2.parseFnModel sym (SMT2.sessionWriter session) synth_fns sexp) return result -- | Run CVC5 SyGuS in a session, with the default configuration. withCVC5_SyGuS :: ExprBuilder t st fs -> FilePath -> LogData -> (SMT2.Session t CVC5_SyGuS -> IO a) -> IO a withCVC5_SyGuS = SMT2.withSolver CVC5_SyGuS nullAcknowledgementAction (SMT2.defaultFeatures CVC5_SyGuS) (Just cvc5StrictParsing) writeCVC5SyFile :: sym ~ ExprBuilder t st fs => sym -> Handle -> [SomeSymFn sym] -> [BoolExpr t] -> IO () writeCVC5SyFile sym h synth_fns constraints = do writer <- SMT2.defaultFileWriter CVC5_SyGuS (show CVC5_SyGuS) (SMT2.defaultFeatures CVC5_SyGuS) (Just cvc5StrictParsing) sym h SMT2.setDefaultLogicAndOptions writer writeSyGuSProblem sym writer synth_fns constraints SMT2.writeExit writer writeSyGuSProblem :: sym ~ ExprBuilder t st fs => sym -> WriterConn t (SMT2.Writer CVC5_SyGuS) -> [SomeSymFn sym] -> [BoolExpr t] -> IO () writeSyGuSProblem sym writer synth_fns constraints = do mapM_ (\(SomeSymFn fn) -> addSynthFun writer fn) synth_fns mapM_ (viewSome $ addDeclareVar writer) $ foldMap (exprUninterpConstants sym) constraints mapM_ (addConstraint writer) constraints SMT2.writeCheckSynth writer