packages feed

what4 1.3 → 1.4

raw patch · 45 files changed

+5182/−85 lines, 45 filesdep +BoundedChandep +megaparsecdep +ordered-containersdep −data-binary-ieee754dep ~basedep ~bv-sizeddep ~containers

Dependencies added: BoundedChan, megaparsec, ordered-containers, parsec, s-cargot, stm, time, unliftio

Dependencies removed: data-binary-ieee754

Dependency ranges changed: base, bv-sized, containers, directory, exceptions, hedgehog, libBF, parameterized-utils, process, tasty, tasty-hedgehog, tasty-hunit, tasty-sugar, temporary, text

Files

CHANGES.md view
@@ -1,3 +1,36 @@+# 1.4 (January 2023)++* Allow building with GHC 9.4.++* Remove the `MonadFail` instance for `VarRecorder`, as this instance is no+  longer straightforward to define due to upstream changes in `base-4.17.0.0`.+  This instance ultimately called `error` anyways, so any uses of `fail` at type+  `VarRecorder` can be replaced with `error` without any change in behavior.++* Remove a dependency on `data-binary-ieee754`, which has been deprecated.++* Deprecate `allSupported` which represents the SMT logic `ALL_SUPPORTED`,+  and add `allLogic` instead which represents the SMTLib standard logic `ALL`.++* Add support for the cvc5 SMT solver.++* Add a `get-abduct` feature which is compatible with cvc5.++* Add modules to support serialization and deserialization of what4 terms into+  an s-expression format that is a superset of SMTLib2. See the+  `What4.Serialize.Printer`, `What4.Serialize.Parser`, and+  `What4.Serialize.FastSExpr` modules. Note that these modules have names that+  conflict with the now deprecated what4-serialize package, from which they were+  copied. If you are updating to this version of what4, delete your dependency+  on what4-serialize.++* Add support Syntax-Guided Synthesis (SyGuS) in CVC5 (through the+  `runCVC5SyGuS` function) and Constrained Horn Clauses (CHC) in Z3 (through the+  `runZ3Horn` function).++* Make `what4` smarter about simplifying `intMin x y` and `intMax x y`+  expressions when either `x <= y` or `y <= x` can be statically determined.+ # 1.3 (April 2022)  * Allow building with GHC 9.2.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2013-2020 Galois Inc.+Copyright (c) 2013-2023 Galois Inc. All rights reserved.  Redistribution and use in source and binary forms, with or without
README.md view
@@ -224,6 +224,12 @@ Additional implementation and operational documentation can be found in the [implementation documentation in doc/implementation.md](doc/implementation.md). +To serialize and deserialize what4 terms, see the following modules:++* `What4.Serialize.Printer` (to serialize what4 terms into an s-expression format)+* `What4.Serialize.Parser` (to deserialize what4 terms)+* `What4.Serialize.FastSExpr` (provides a faster s-expression parser than the default, intended to be used in conjunction with the higher-level parsing in `What4.Serialize.Parser`)+ ## Formula Construction vs Solving  In what4, building expressions and solving expressions are orthogonal concerns.@@ -273,6 +279,7 @@ - Z3 versions 4.8.7 through 4.8.12 - Yices 2.6.1 and 2.6.2 - CVC4 1.7 and 1.8+- CVC5 1.0.2 - Boolector 3.2.1 and 3.2.2 - STP 2.3.3     (However, note https://github.com/stp/stp/issues/363, which prevents
doc/implementation.md view
@@ -2,7 +2,7 @@  What4 provides a language to represent symbolic computations and the ability to perform those computations using one of several SMT-solvers, including Yices, Z3, CVC4, and others.+solvers, including Yices, Z3, CVC4, CVC5, and others.  ## What4 Language 
src/What4/Expr/App.hs view
@@ -1722,6 +1722,8 @@ instance IsSymFn (ExprSymFn t) where   fnArgTypes = symFnArgTypes   fnReturnType = symFnReturnType+  fnTestEquality = testExprSymFnEq+  fnCompare f g = compareF (symFnId f) (symFnId g)   -------------------------------------------------------------------------------
src/What4/Expr/AppTheory.hs view
@@ -33,7 +33,7 @@    | FloatingPointTheory    | ArrayTheory    | StructTheory-     -- ^ Theory attributed to structs (equivalent to records in CVC4/Z3, tuples in Yices)+     -- ^ Theory attributed to structs (equivalent to records in CVC4/CVC5/Z3, tuples in Yices)    | FnTheory      -- ^ Theory attributed application functions.    deriving (Eq, Ord)
src/What4/Expr/Builder.hs view
@@ -134,6 +134,8 @@   , SymFnInfo(..)   , symFnArgTypes   , symFnReturnType+  , SomeExprSymFn(..)+  , ExprSymFnWrapper(..)      -- * SymbolVarBimap   , SymbolVarBimap@@ -187,7 +189,6 @@ import qualified Data.BitVector.Sized as BV import           Data.Bimap (Bimap) import qualified Data.Bimap as Bimap-import qualified Data.Binary.IEEE754 as IEEE754  import           Data.Hashable import           Data.IORef@@ -208,6 +209,7 @@ import           Data.Ratio (numerator, denominator) import           Data.Set (Set) import qualified Data.Set as Set+import           GHC.Float (castFloatToWord32, castDoubleToWord64) import qualified LibBF as BF  import           What4.BaseTypes@@ -247,19 +249,19 @@ toDouble :: Rational -> Double toDouble = fromRational -cachedEval :: (HashableF k, TestEquality k)+cachedEval :: (HashableF k, TestEquality k, MonadIO m)            => PH.HashTable RealWorld k a            -> k tp-           -> IO (a tp)-           -> IO (a tp)+           -> m (a tp)+           -> m (a tp) cachedEval tbl k action = do-  mr <- stToIO $ PH.lookup tbl k+  mr <- liftIO $ stToIO $ PH.lookup tbl k   case mr of     Just r -> return r     Nothing -> do       r <- action       seq r $ do-      stToIO $ PH.insert tbl k r+      liftIO $ stToIO $ PH.insert tbl k r       return r  ------------------------------------------------------------------------@@ -319,8 +321,20 @@ data ExprSymFnWrapper t c    = forall a r . (c ~ (a ::> r)) => ExprSymFnWrapper (ExprSymFn t a r) -data SomeSymFn sym = forall args ret . SomeSymFn (SymFn sym args ret)+data SomeExprSymFn t = forall args ret . SomeExprSymFn (ExprSymFn t args ret) +instance Eq (SomeExprSymFn t) where+  (SomeExprSymFn fn1) == (SomeExprSymFn fn2) =+    isJust $ fnTestEquality fn1 fn2++instance Ord (SomeExprSymFn t) where+  compare (SomeExprSymFn fn1) (SomeExprSymFn fn2) =+    toOrdering $ fnCompare fn1 fn2++instance Show (SomeExprSymFn t) where+  show (SomeExprSymFn f) = show f++ ------------------------------------------------------------------------ -- ExprBuilder @@ -751,12 +765,12 @@ -- -- It is used when an action may modify a value, and we only want to run a -- second action if the value changed.-runIfChanged :: Eq e+runIfChanged :: (Eq e, Monad m)              => e-             -> (e -> IO e) -- ^ First action to run+             -> (e -> m e) -- ^ First action to run              -> r           -- ^ Result if no change.-             -> (e -> IO r) -- ^ Second action to run-             -> IO r+             -> (e -> m r) -- ^ Second action to run+             -> m r runIfChanged x f unChanged onChange = do   y <- f x   if x == y then@@ -807,11 +821,14 @@              -> ExprBuilder t st fs              -> ExprSymFn t idx ret              -> IO (Bool,ExprSymFn t idx ret)-evalSimpleFn tbl sym f =+evalSimpleFn tbl sym f = do+  let n = symFnId f   case symFnInfo f of-    UninterpFnInfo{} -> return (False, f)+    UninterpFnInfo{} -> do+      CachedSymFn changed f' <- cachedEval (fnTable tbl) n $+        return $! CachedSymFn False f+      return (changed, f')     DefinedFnInfo vars e evalFn -> do-      let n = symFnId f       let nm = symFnName f       CachedSymFn changed f' <-         cachedEval (fnTable tbl) n $ do@@ -3668,10 +3685,10 @@   iFloatLitRational sym fi x = iRealToFloat sym fi RNE =<< realLit sym x   iFloatLitSingle sym x =     iFloatFromBinary sym SingleFloatRepr-      =<< (bvLit sym knownNat $ BV.word32 $ IEEE754.floatToWord x)+      =<< (bvLit sym knownNat $ BV.word32 $ castFloatToWord32 x)   iFloatLitDouble sym x =     iFloatFromBinary sym DoubleFloatRepr-      =<< (bvLit sym knownNat $ BV.word64 $ IEEE754.doubleToWord x)+      =<< (bvLit sym knownNat $ BV.word64 $ castDoubleToWord64 x)   iFloatLitLongDouble sym x =     iFloatFromBinary sym X86_80FloatRepr       =<< (bvLit sym knownNat $ BV.mkBV knownNat $ fp80ToBits x)@@ -3853,10 +3870,10 @@   iFloatLitRational sym = floatLitRational sym . floatInfoToPrecisionRepr   iFloatLitSingle sym x =     floatFromBinary sym knownRepr-      =<< (bvLit sym knownNat $ BV.word32 $ IEEE754.floatToWord x)+      =<< (bvLit sym knownNat $ BV.word32 $ castFloatToWord32 x)   iFloatLitDouble sym x =     floatFromBinary sym knownRepr-      =<< (bvLit sym knownNat $ BV.word64 $ IEEE754.doubleToWord x)+      =<< (bvLit sym knownNat $ BV.word64 $ castDoubleToWord64 x)   iFloatLitLongDouble sym (X86_80Val e s) = do     el <- bvLit sym (knownNat @16) $ BV.word16 e     sl <- bvLit sym (knownNat @64) $ BV.word64 s@@ -4027,6 +4044,30 @@      MatlabSolverFnInfo f _ _ -> do        evalMatlabSolverFn f sym args      _ -> sbNonceExpr sym $! FnApp fn args++  substituteBoundVars sym subst e = do+    tbls <- stToIO $ do+      expr_tbl <- PH.newSized $ PM.size subst+      fn_tbl <- PH.new+      PM.traverseWithKey_ (PH.insert expr_tbl . BoundVarExpr) subst+      return $ EvalHashTables+        { exprTable = expr_tbl+        , fnTable  = fn_tbl+        }+    evalBoundVars' tbls sym e++  substituteSymFns sym subst e = do+    tbls <- stToIO $ do+      expr_tbl <- PH.new+      fn_tbl <- PH.newSized $ PM.size subst+      PM.traverseWithKey_+        (\(SymFnWrapper f) (SymFnWrapper g) -> PH.insert fn_tbl (symFnId f) (CachedSymFn True g))+        subst+      return $ EvalHashTables+        { exprTable = expr_tbl+        , fnTable  = fn_tbl+        }+    evalBoundVars' tbls sym e   instance IsInterpretedFloatExprBuilder (ExprBuilder t st fs) => IsInterpretedFloatSymExprBuilder (ExprBuilder t st fs)
src/What4/Expr/VarIdentification.hs view
@@ -138,7 +138,6 @@   deriving ( Functor            , Applicative            , Monad-           , MonadFail            , MonadST s            ) @@ -194,7 +193,7 @@   VR $ existQuantifiers %= Map.insert e (Some info)   recordAssertionVars ExistsOnly p x addExistVar ExistsForall _ _ _ _ _ = do-  fail $ "what4 does not allow existental variables to appear inside forall quantifier."+  error $ "what4 does not allow existental variables to appear inside forall quantifier."  addForallVar :: BM.Polarity -- ^ Polarity of formula              -> NonceAppExpr t BaseBoolType -- ^ Top term@@ -229,7 +228,7 @@   VR $ forallQuantifiers %= Map.insert e (Some info)   recordExprVars ExistsForall x addBothVar ExistsForall _ _ _ _ = do-  fail $ "what4 does not allow existental variables to appear inside forall quantifier."+  error $ "what4 does not allow existental variables to appear inside forall quantifier."  -- | Record variables in a predicate that we are checking satisfiability of. recordAssertionVars :: Scope
src/What4/Interface.hs view
@@ -38,6 +38,12 @@    [@instance 'HashableF' ('SymExpr' sym)@] +  [@instance 'OrdF' ('BoundVar' sym)@]++  [@instance 'TestEquality' ('BoundVar' sym)@]++  [@instance 'HashableF' ('BoundVar' sym)@]+ The canonical implementation of these interface classes is found in "What4.Expr.Builder". -} {-# LANGUAGE CPP #-}@@ -72,6 +78,8 @@     -- ** Expression recognizers   , IsExpr(..)   , IsSymFn(..)+  , SomeSymFn(..)+  , SymFnWrapper(..)   , UnfoldPolicy(..)   , shouldUnfold @@ -202,6 +210,7 @@ import qualified Data.Parameterized.Context as Ctx import           Data.Parameterized.Ctx import           Data.Parameterized.Utils.Endian (Endian(..))+import           Data.Parameterized.Map (MapF) import           Data.Parameterized.NatRepr import           Data.Parameterized.TraversableFC import qualified Data.Parameterized.Vector as Vector@@ -587,7 +596,7 @@ -- of an undefined function is _not_ guaranteed to be equivalant to a free -- constant, and no guarantees are made about what properties such values -- will satisfy.-class ( IsExpr (SymExpr sym), HashableF (SymExpr sym)+class ( IsExpr (SymExpr sym), HashableF (SymExpr sym), HashableF (BoundVar sym)       , TestEquality (SymAnnotation sym), OrdF (SymAnnotation sym)       , HashableF (SymAnnotation sym)       ) => IsExprBuilder sym where@@ -740,14 +749,42 @@   -- | Return the minimum value of two integers.   intMin :: sym -> SymInteger sym -> SymInteger sym -> IO (SymInteger sym)   intMin sym x y =-    do p <- intLe sym x y-       intIte sym p x y+    do x_le_y <- intLe sym x y+       y_le_x <- intLe sym y x+       case (asConstantPred x_le_y, asConstantPred y_le_x) of+         -- x <= y+         (Just True, _) -> return x+         -- x < y+         (_, Just False) -> return x+         -- y < x+         (Just False, _) -> return y+         -- y <= x+         (_, Just True) -> return y+         _ ->+           do let rng_x = integerBounds x+              let rng_y = integerBounds y+              unsafeSetAbstractValue (rangeMin rng_x rng_y) <$>+                intIte sym x_le_y x y    -- | Return the maximum value of two integers.   intMax :: sym -> SymInteger sym -> SymInteger sym -> IO (SymInteger sym)   intMax sym x y =-    do p <- intLe sym x y-       intIte sym p y x+    do x_le_y <- intLe sym x y+       y_le_x <- intLe sym y x+       case (asConstantPred x_le_y, asConstantPred y_le_x) of+         -- x <= y+         (Just True, _) -> return y+         -- x < y+         (_, Just False) -> return y+         -- y < x+         (Just False, _) -> return x+         -- y <= x+         (_, Just True) -> return x+         _ ->+           do let rng_x = integerBounds x+              let rng_y = integerBounds y+              unsafeSetAbstractValue (rangeMax rng_x rng_y) <$>+                intIte sym x_le_y y x    -- | If-then-else applied to integers.   intIte :: sym -> Pred sym -> SymInteger sym -> SymInteger sym -> IO (SymInteger sym)@@ -2690,15 +2727,43 @@ -- 'IsSymExprBuilder'. type family SymFn sym :: Ctx BaseType -> BaseType -> Type +data SomeSymFn sym = forall args ret . SomeSymFn (SymFn sym args ret)++-- | Wrapper for `SymFn` that concatenates the arguments and the return types.+--+-- This is useful for implementing `TestEquality` and `OrdF` instances for+-- `SymFn`, and for using `SymFn` as a key or a value in a `MapF`.+data SymFnWrapper sym ctx where+  SymFnWrapper :: forall sym args ret . SymFn sym args ret -> SymFnWrapper sym (args ::> ret)++instance IsSymFn (SymFn sym) => TestEquality (SymFnWrapper sym) where+  testEquality (SymFnWrapper fn1) (SymFnWrapper fn2) = fnTestEquality fn1 fn2++instance IsSymFn (SymFn sym) => OrdF (SymFnWrapper sym) where+  compareF (SymFnWrapper fn1) (SymFnWrapper fn2) = fnCompare fn1 fn2+ -- | A class for extracting type representatives from symbolic functions-class IsSymFn fn where+class IsSymFn (fn :: Ctx BaseType -> BaseType -> Type) where   -- | Get the argument types of a function.   fnArgTypes :: fn args ret -> Ctx.Assignment BaseTypeRepr args    -- | Get the return type of a function.   fnReturnType :: fn args ret -> BaseTypeRepr ret +  -- | Test whether two functions are equal.+  --+  -- The implementation may be incomplete, that is, if it returns `Just` then+  -- the functions are equal, while if it returns `Nothing` then the functions+  -- may or may not be equal. The result of `freshTotalUninterpFn` or+  -- `definedFn` tests equal with itself.+  fnTestEquality :: fn args1 ret1 -> fn args2 ret2 -> Maybe ((args1 ::> ret1) :~: (args2 ::> ret2)) +  -- | Compare two functions for ordering.+  --+  -- The underlying equality test is provided by `fnTestEquality`.+  fnCompare :: fn args1 ret1 -> fn args2 ret2 -> OrderingF (args1 ::> ret1) (args2 ::> ret2)++ -- | Describes when we unfold the body of defined functions. data UnfoldPolicy   = NeverUnfold@@ -2741,6 +2806,7 @@ class ( IsExprBuilder sym       , IsSymFn (SymFn sym)       , OrdF (SymExpr sym)+      , OrdF (BoundVar sym)       ) => IsSymExprBuilder sym where    ----------------------------------------------------------------------@@ -2885,6 +2951,22 @@              -> Ctx.Assignment (SymExpr sym) args                 -- ^ Arguments to function              -> IO (SymExpr sym ret)++  -- | Apply a variable substitution (variable to symbolic expression mapping)+  -- to a symbolic expression.+  substituteBoundVars ::+    sym ->+    MapF (BoundVar sym) (SymExpr sym) ->+    SymExpr sym tp ->+    IO (SymExpr sym tp)++  -- | Apply a function substitution (function to function mapping) to a+  -- symbolic expression.+  substituteSymFns ::+    sym ->+    MapF (SymFnWrapper sym) (SymFnWrapper sym) ->+    SymExpr sym tp ->+    IO (SymExpr sym tp)  -- | This returns true if the value corresponds to a concrete value. baseIsConcrete :: forall e bt
src/What4/ProblemFeatures.hs view
@@ -45,6 +45,7 @@   , useUnsatAssumptions   , useUninterpFunctions   , useDefinedFunctions+  , useProduceAbducts   , hasProblemFeature   ) where @@ -93,20 +94,20 @@  -- | Indicates whether the problem uses structs ----- Structs are modeled using constructors in CVC4/Z3, and tuples+-- Structs are modeled using constructors in CVC4/CVC5/Z3, and tuples -- in Yices. useStructs :: ProblemFeatures useStructs = ProblemFeatures 0x100  -- | Indicates whether the problem uses strings -----   Strings have some symbolic support in CVC4 and Z3.+--   Strings have some symbolic support in CVC4, CVC5, and Z3. useStrings :: ProblemFeatures useStrings = ProblemFeatures 0x200  -- | Indicates whether the problem uses floating-point -----   Floating-point has some symbolic support in CVC4 and Z3.+--   Floating-point has some symbolic support in CVC4, CVC5, and Z3. useFloatingPoint :: ProblemFeatures useFloatingPoint = ProblemFeatures 0x400 @@ -129,6 +130,11 @@ --   defined functions. useDefinedFunctions :: ProblemFeatures useDefinedFunctions = ProblemFeatures 0x4000++-- | Indicates if the solver is able and configured to +--   produce abducts.+useProduceAbducts :: ProblemFeatures+useProduceAbducts = ProblemFeatures 0x8000  -- | Tests if one set of problem features subsumes another. --   In particular, @hasProblemFeature x y@ is true iff
src/What4/Protocol/Online.hs view
@@ -32,12 +32,15 @@   , reset   , inNewFrame   , inNewFrameWithVars+  , inNewFrame2Open+  , inNewFrame2Close   , check   , checkAndGetModel   , checkWithAssumptions   , checkWithAssumptionsAndModel   , getModel   , getUnsatCore+  , getAbducts   , getUnsatAssumptions   , getSatResult   , checkSatisfiable@@ -236,6 +239,39 @@         do assume conn p            check proc rsn +-- | @get-abuct nm t@ queries the solver for the first abduct, which is returned+--   as an SMT function definition named @nm@. The remaining abducts are obtained+--   from the solver by successive invocations of the @get-abduct-next@ command,+--   which return SMT functions bound to the same @nm@ as the first. The name @nm@+--   is bound within the current assertion frame.+--   Note that this is an unstable API; we expect that the return type will change +--   to a parsed expression in the future+getAbducts ::+  SMTReadWriter solver =>+  SolverProcess scope solver ->+  Int ->+  Text ->+  BoolExpr scope ->+  IO [String]+getAbducts proc n nm t =+  if (n > 0) then do +    let conn = solverConn proc+    unless (supportedFeatures conn `hasProblemFeature` useProduceAbducts) $+      fail $ show $ pretty (smtWriterName conn) <+> pretty "is not configured to produce abducts"+    f <- mkFormula conn t+    -- get the first abduct using the get-abduct command+    addCommandNoAck conn (getAbductCommand conn nm f)+    abd1 <- smtAbductResult conn conn nm f+    -- get the remaining abducts using get-abduct-next commands+    if (n > 1) then do+      let rest = n - 1+      abdRest <- forM [1..rest] $ \_ -> do+        addCommandNoAck conn (getAbductNextCommand conn)+        smtAbductNextResult conn conn+      return (abd1:abdRest)+    else return [abd1]+  else return []+ -- | Check if the formula is satisifiable in the current --   solver state.  This is done in a --   fresh frame, which is exited after the continuation@@ -326,6 +362,17 @@ -- | Perform an action in the scope of a solver assumption frame. inNewFrame :: (MonadIO m, MonadMask m, SMTReadWriter solver) => SolverProcess scope solver -> m a -> m a inNewFrame p action = inNewFrameWithVars p [] action++-- | Open a second solver assumption frame.+-- For abduction, we want the final assertion to be a in a new frame, so that it +-- can be closed before asking for abducts. The following two commands allow frame 2 +-- to be pushed and popped independently of other commands+inNewFrame2Open :: SMTReadWriter solver => SolverProcess scope solver -> IO ()+inNewFrame2Open sp = let c = solverConn sp in addCommand c (push2Command c)++-- | Close a second solver assumption frame.+inNewFrame2Close :: SMTReadWriter solver => SolverProcess scope solver -> IO ()+inNewFrame2Close sp = let c = solverConn sp in addCommand c (pop2Command c)  -- | Perform an action in the scope of a solver assumption frame, where the given -- bound variables are considered free within that frame.
src/What4/Protocol/SExp.hs view
@@ -19,6 +19,7 @@   , asAtomList   , asNegAtomList   , skipSpaceOrNewline+  , sExpToString   ) where  #if !MIN_VERSION_base(4,13,0)@@ -111,3 +112,8 @@   go (SAtom a:ys) = (a:) <$> go ys   go _ = Nothing asAtomList _ = Nothing++sExpToString :: SExp -> String+sExpToString (SAtom t) = Text.unpack t+sExpToString (SString t) = ('"' : Text.unpack t) ++ ['"']+sExpToString (SApp ss) = ('(' : Data.String.unwords (map sExpToString ss)) ++ [')']
src/What4/Protocol/SMTLib2.hs view
@@ -15,9 +15,12 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-}@@ -38,7 +41,11 @@   , writeCheckSat   , writeExit   , writeGetValue+  , writeGetAbduct+  , writeGetAbductNext+  , writeCheckSynth   , runCheckSat+  , runGetAbducts   , asSMT2Type   , setOption   , getVersion@@ -48,10 +55,13 @@   , setProduceModels   , smtLibEvalFuns   , smtlib2Options+  , parseFnModel+  , parseFnValues     -- * Logic   , SMT2.Logic(..)   , SMT2.qf_bv   , SMT2.allSupported+  , SMT2.hornLogic   , all_supported   , setLogic     -- * Type@@ -66,6 +76,7 @@   , Session(..)   , SMTLib2GenericSolver(..)   , writeDefaultSMT2+  , defaultFileWriter   , startSolver   , shutdownSolver   , smtAckResult@@ -90,14 +101,21 @@  import           Control.Applicative import           Control.Exception-import           Control.Monad.State.Strict+import           Control.Monad.Except+import           Control.Monad.Reader+import qualified Data.Bimap as Bimap import qualified Data.BitVector.Sized as BV import           Data.Char (digitToInt, isAscii)+import           Data.HashMap.Lazy (HashMap)+import qualified Data.HashMap.Lazy as HashMap import           Data.IORef import           Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import           Data.Monoid+import           Data.Parameterized.Classes import qualified Data.Parameterized.Context as Ctx+import           Data.Parameterized.Map (MapF)+import qualified Data.Parameterized.Map as MapF import           Data.Parameterized.NatRepr import           Data.Parameterized.Pair import           Data.Parameterized.Some@@ -121,6 +139,7 @@ import           Data.Versions (Version(..)) import qualified Data.Versions as Versions import qualified Prettyprinter as PP+import           Text.Printf (printf) import           LibBF( bfToBits )  import           Prelude hiding (writeFile)@@ -149,8 +168,8 @@  -- | Set the logic to all supported logics. all_supported :: SMT2.Logic-all_supported = SMT2.allSupported-{-# DEPRECATED all_supported "Use allSupported" #-}+all_supported = SMT2.allLogic+{-# DEPRECATED all_supported "Use allLogic instead" #-}   smtlib2Options :: [CFG.ConfigDesc]@@ -676,6 +695,8 @@    pushCommand _  = SMT2.push 1   popCommand _   = SMT2.pop 1+  push2Command _ = SMT2.push 2+  pop2Command _ = SMT2.pop 2   resetCommand _ = SMT2.resetAssertions   popManyCommands _ n = [SMT2.pop (toInteger n)] @@ -684,6 +705,9 @@    getUnsatAssumptionsCommand _ = SMT2.getUnsatAssumptions   getUnsatCoreCommand _ = SMT2.getUnsatCore+  getAbductCommand _ nm e = SMT2.getAbduct nm e+  getAbductNextCommand _ = SMT2.getAbductNext+     setOptCommand _ = SMT2.setOption    declareCommand _proxy v argTypes retType =@@ -693,6 +717,11 @@     let resolveArg (var, Some tp) = (var, asSMT2Type @a tp)      in SMT2.defineFun f (resolveArg <$> args) (asSMT2Type @a return_type) e +  synthFunCommand _proxy f args ret_tp =+    SMT2.synthFun f (map (\(var, Some tp) -> (var, asSMT2Type @a tp)) args) (asSMT2Type @a ret_tp)+  declareVarCommand _proxy v tp = SMT2.declareVar v (asSMT2Type @a tp)+  constraintCommand _proxy e = SMT2.constraint e+   stringTerm str = smtlib2StringTerm @a str   stringLength x = smtlib2StringLength @a x   stringAppend xs = smtlib2StringAppend @a xs@@ -755,6 +784,16 @@ writeGetValue :: SMTLib2Tweaks a => WriterConn t (Writer a) -> [Term] -> IO () writeGetValue w l = addCommandNoAck w $ SMT2.getValue l +writeGetAbduct :: SMTLib2Tweaks a => WriterConn t (Writer a) -> Text -> Term -> IO ()+writeGetAbduct w nm p = addCommandNoAck w $ SMT2.getAbduct nm p++writeGetAbductNext :: SMTLib2Tweaks a => WriterConn t (Writer a) -> IO ()+writeGetAbductNext w = addCommandNoAck w SMT2.getAbductNext++-- | Write check-synth command+writeCheckSynth :: SMTLib2Tweaks a => WriterConn t (Writer a) -> IO ()+writeCheckSynth w = addCommandNoAck w SMT2.checkSynth+ parseBoolSolverValue :: MonadFail m => SExp -> m Bool parseBoolSolverValue (SAtom "true")  = return True parseBoolSolverValue (SAtom "false") = return False@@ -762,6 +801,16 @@   do v <- parseBvSolverValue (knownNat @1) s      return (if v == BV.zero knownNat then False else True) +parseIntSolverValue :: MonadFail m => SExp -> m Integer+parseIntSolverValue = \case+  SAtom v+    | [(i, "")] <- readDec (Text.unpack v) ->+      return i+  SApp ["-", x] ->+    negate <$> parseIntSolverValue x+  s ->+    fail $ "Could not parse solver value: " ++ show s+ parseRealSolverValue :: MonadFail m => SExp -> m Rational parseRealSolverValue (SAtom v) | Just (r,"") <- readDecimal (Text.unpack v) =   return r@@ -778,10 +827,11 @@ -- of the variable. parseBvSolverValue :: MonadFail m => NatRepr w -> SExp -> m (BV.BV w) parseBvSolverValue w s-  | Pair w' bv <- parseBVLitHelper s = case w' `compareNat` w of+  | Just (Pair w' bv) <- parseBVLitHelper s = case w' `compareNat` w of       NatLT zw -> return (BV.zext (addNat w' (addNat zw knownNat)) bv)       NatEQ -> return bv       NatGT _ -> return (BV.trunc w bv)+  | otherwise = fail $ "Could not parse bitvector solver value: " ++ show s  natBV :: Natural       -- ^ width@@ -792,15 +842,14 @@   Some w -> Pair w (BV.mkBV w x)  -- | Parse an s-expression and return a bitvector and its width-parseBVLitHelper :: SExp -> Pair NatRepr BV.BV+parseBVLitHelper :: SExp -> Maybe (Pair NatRepr BV.BV) parseBVLitHelper (SAtom (Text.unpack -> ('#' : 'b' : n_str))) | [(n, "")] <- readBin n_str =-  natBV (fromIntegral (length n_str)) n+  Just $ natBV (fromIntegral (length n_str)) n parseBVLitHelper (SAtom (Text.unpack -> ('#' : 'x' : n_str))) | [(n, "")] <- readHex n_str =-  natBV (fromIntegral (length n_str * 4)) n+  Just $ natBV (fromIntegral (length n_str * 4)) n parseBVLitHelper (SApp ["_", SAtom (Text.unpack -> ('b' : 'v' : n_str)), SAtom (Text.unpack -> w_str)])-  | [(n, "")] <- readDec n_str, [(w, "")] <- readDec w_str = natBV w n--- BGS: Is this correct?-parseBVLitHelper _ = natBV 0 0+  | [(n, "")] <- readDec n_str, [(w, "")] <- readDec w_str = Just $ natBV w n+parseBVLitHelper _ = Nothing  parseStringSolverValue :: MonadFail m => SExp -> m Text parseStringSolverValue (SString t) | Just t' <- unescapeText t = return t'@@ -831,10 +880,10 @@  parseFloatLitHelper :: MonadFail m => SExp -> m ParsedFloatResult parseFloatLitHelper (SApp ["fp", sign_s, expt_s, scand_s])-  | Pair sign_w sign <- parseBVLitHelper sign_s+  | Just (Pair sign_w sign) <- parseBVLitHelper sign_s   , Just Refl <- sign_w `testEquality` (knownNat @1)-  , Pair eb expt <- parseBVLitHelper expt_s-  , Pair sb scand <- parseBVLitHelper scand_s+  , Just (Pair eb expt) <- parseBVLitHelper expt_s+  , Just (Pair sb scand) <- parseBVLitHelper scand_s   = return $ ParsedFloatResult sign eb expt sb scand parseFloatLitHelper   s@(SApp ["_", SAtom (Text.unpack -> nm), SAtom (Text.unpack -> eb_s), SAtom (Text.unpack -> sb_s)])@@ -870,6 +919,341 @@     _ -> return Nothing parseBvArraySolverValue _ _ _ = return Nothing +parseFnModel ::+  sym ~ B.ExprBuilder t st fs  =>+  sym ->+  WriterConn t h ->+  [I.SomeSymFn sym] ->+  SExp ->+  IO (MapF (I.SymFnWrapper sym) (I.SymFnWrapper sym))+parseFnModel = parseFns parseDefineFun++parseFnValues ::+  sym ~ B.ExprBuilder t st fs  =>+  sym ->+  WriterConn t h ->+  [I.SomeSymFn sym] ->+  SExp ->+  IO (MapF (I.SymFnWrapper sym) (I.SymFnWrapper sym))+parseFnValues = parseFns parseLambda++parseFns ::+  sym ~ B.ExprBuilder t st fs =>+  (sym -> SExp -> IO (Text, I.SomeSymFn sym)) ->+  sym ->+  WriterConn t h ->+  [I.SomeSymFn sym] ->+  SExp ->+  IO (MapF (I.SymFnWrapper sym) (I.SymFnWrapper sym))+parseFns parse_model_fn sym conn uninterp_fns sexp = do+  fn_name_bimap <- cacheLookupFnNameBimap conn $ map (\(I.SomeSymFn fn) -> B.SomeExprSymFn fn) uninterp_fns+  defined_fns <- case sexp of+    SApp sexps -> Map.fromList <$> mapM (parse_model_fn sym) sexps+    _ -> fail $ "Could not parse model response: " ++ show sexp+  MapF.fromList <$> mapM+    (\(I.SomeSymFn uninterp_fn) -> if+      | Just nm <- Bimap.lookup (B.SomeExprSymFn uninterp_fn) fn_name_bimap+      , Just (I.SomeSymFn defined_fn) <- Map.lookup nm defined_fns+      , Just Refl <- testEquality (I.fnArgTypes uninterp_fn) (I.fnArgTypes defined_fn)+      , Just Refl <- testEquality (I.fnReturnType uninterp_fn) (I.fnReturnType defined_fn) ->+        return $ MapF.Pair (I.SymFnWrapper uninterp_fn) (I.SymFnWrapper defined_fn)+      | otherwise -> fail $ "Could not find model for function: " ++ show uninterp_fn)+    uninterp_fns++parseDefineFun :: I.IsSymExprBuilder sym => sym -> SExp -> IO (Text, I.SomeSymFn sym)+parseDefineFun sym sexp = case sexp of+  SApp ["define-fun", SAtom nm, SApp params_sexp, _ret_type_sexp , body_sexp] -> do+    fn <- parseFn sym nm params_sexp body_sexp+    return (nm, fn)+  _ -> fail $ "unexpected sexp, expected define-fun, found " ++ show sexp++parseLambda :: I.IsSymExprBuilder sym => sym -> SExp -> IO (Text, I.SomeSymFn sym)+parseLambda sym sexp = case sexp of+  SApp [SAtom nm, SApp ["lambda", SApp params_sexp, body_sexp]] -> do+    fn <- parseFn sym nm params_sexp body_sexp+    return (nm, fn)+  _ -> fail $ "unexpected sexp, expected lambda, found " ++ show sexp++parseFn :: I.IsSymExprBuilder sym => sym -> Text -> [SExp] -> SExp -> IO (I.SomeSymFn sym)+parseFn sym nm params_sexp body_sexp = do+  (nms, vars) <- unzip <$> mapM (parseVar sym) params_sexp+  case Ctx.fromList vars of+    Some vars_assign -> do+      let let_env = HashMap.fromList $ zip nms $ map (mapSome $ I.varExpr sym) vars+      proc_res <- runProcessor (ProcessorEnv { procSym = sym, procLetEnv = let_env }) $ parseExpr sym body_sexp+      Some body_expr <- either fail return proc_res+      I.SomeSymFn <$> I.definedFn sym (I.safeSymbol $ Text.unpack nm) vars_assign body_expr I.NeverUnfold++parseVar :: I.IsSymExprBuilder sym => sym -> SExp -> IO (Text, Some (I.BoundVar sym))+parseVar sym sexp = case sexp of+  SApp [SAtom nm, tp_sexp] -> do+    Some tp <- parseType tp_sexp+    var <- liftIO $ I.freshBoundVar sym (I.safeSymbol $ Text.unpack nm) tp+    return (nm, Some var)+  _ -> fail $ "unexpected variable " ++ show sexp++parseType :: SExp -> IO (Some BaseTypeRepr)+parseType sexp = case sexp of+  "Bool" -> return $ Some BaseBoolRepr+  "Int" -> return $ Some BaseIntegerRepr+  "Real" -> return $ Some BaseRealRepr+  SApp ["_", "BitVec", SAtom (Text.unpack -> m_str)]+    | [(m_n, "")] <- readDec m_str+    , Some m <- mkNatRepr m_n+    , Just LeqProof <- testLeq (knownNat @1) m ->+      return $ Some $ BaseBVRepr m+  SApp ["_", "FloatingPoint", SAtom (Text.unpack -> eb_str), SAtom (Text.unpack -> sb_str)]+    | [(eb_n, "")] <- readDec eb_str+    , Some eb <- mkNatRepr eb_n+    , Just LeqProof <- testLeq (knownNat @2) eb+    , [(sb_n, "")] <- readDec sb_str+    , Some sb <- mkNatRepr sb_n+    , Just LeqProof <- testLeq (knownNat @2) sb ->+      return $ Some $ BaseFloatRepr $ FloatingPointPrecisionRepr eb sb+  SApp ["Array", idx_tp_sexp, val_tp_sexp] -> do+    Some idx_tp <- parseType idx_tp_sexp+    Some val_tp <- parseType val_tp_sexp+    return $ Some $ BaseArrayRepr (Ctx.singleton idx_tp) val_tp+  _ -> fail $ "unexpected type " ++ show sexp+++-- | Stores a NatRepr along with proof that its type parameter is a bitvector of+-- that length. Used for easy pattern matching on the LHS of a binding in a+-- do-expression to extract the proof.+data BVProof tp where+  BVProof :: forall n . (1 <= n) => NatRepr n -> BVProof (BaseBVType n)++-- | Given an expression, monadically either returns proof that it is a+-- bitvector or throws an error.+getBVProof :: (I.IsExpr ex, MonadError String m) => ex tp -> m (BVProof tp)+getBVProof expr = case I.exprType expr of+  BaseBVRepr n -> return $ BVProof n+  t -> throwError $ "expected BV, found " ++ show t++-- | Operator type descriptions for parsing s-expression of+-- the form @(operator operands ...)@.+--+-- Code is copy-pasted and adapted from `What4.Serialize.Parser`, see+-- <https://github.com/GaloisInc/what4/issues/228>+data Op sym where+    -- | Generic unary operator description.+    Op1 ::+      Ctx.Assignment BaseTypeRepr (Ctx.EmptyCtx Ctx.::> arg1) ->+      (sym -> I.SymExpr sym arg1 -> IO (I.SymExpr sym ret)) ->+      Op sym+    -- | Generic binary operator description.+    Op2 ::+      Ctx.Assignment BaseTypeRepr (Ctx.EmptyCtx Ctx.::> arg1 Ctx.::> arg2) ->+      Maybe Assoc ->+      (sym -> I.SymExpr sym arg1 -> I.SymExpr sym arg2 -> IO (I.SymExpr sym ret)) ->+      Op sym+    -- | Encapsulating type for a unary operation that takes one bitvector and+    -- returns another (in IO).+    BVOp1 ::+      (forall w . (1 <= w) => sym -> I.SymBV sym w -> IO (I.SymBV sym w)) ->+      Op sym+    -- | Binop with a bitvector return type, e.g., addition or bitwise operations.+    BVOp2 ::+      Maybe Assoc ->+      (forall w . (1 <= w) => sym -> I.SymBV sym w -> I.SymBV sym w -> IO (I.SymBV sym w)) ->+      Op sym+    -- | Bitvector binop with a boolean return type, i.e., comparison operators.+    BVComp2 ::+      (forall w . (1 <= w) => sym -> I.SymBV sym w -> I.SymBV sym w -> IO (I.Pred sym)) ->+      Op sym++data Assoc = RightAssoc | LeftAssoc++newtype Processor sym a = Processor (ExceptT String (ReaderT (ProcessorEnv sym) IO) a)+  deriving (Functor, Applicative, Monad, MonadIO, MonadError String, MonadReader (ProcessorEnv sym))++data ProcessorEnv sym = ProcessorEnv+  { procSym :: sym+  , procLetEnv :: HashMap Text (Some (I.SymExpr sym))+  }++runProcessor :: ProcessorEnv sym -> Processor sym a -> IO (Either String a)+runProcessor env (Processor action) = runReaderT (runExceptT action) env++opTable :: I.IsSymExprBuilder sym => HashMap Text (Op sym)+opTable = HashMap.fromList+  -- Boolean ops+  [ ("not", Op1 knownRepr I.notPred)+  , ("=>", Op2 knownRepr (Just RightAssoc) I.impliesPred)+  , ("and", Op2 knownRepr (Just LeftAssoc) I.andPred)+  , ("or", Op2 knownRepr (Just LeftAssoc) I.orPred)+  , ("xor", Op2 knownRepr (Just LeftAssoc) I.xorPred)+  -- Integer ops+  , ("-", Op2 knownRepr (Just LeftAssoc) I.intSub)+  , ("+", Op2 knownRepr (Just LeftAssoc) I.intAdd)+  , ("*", Op2 knownRepr (Just LeftAssoc) I.intMul)+  , ("div", Op2 knownRepr (Just LeftAssoc) I.intDiv)+  , ("mod", Op2 knownRepr Nothing I.intMod)+  , ("abs", Op1 knownRepr I.intAbs)+  , ("<=", Op2 knownRepr Nothing I.intLe)+  , ("<", Op2 knownRepr Nothing I.intLt)+  , (">=", Op2 knownRepr Nothing $ \sym arg1 arg2 -> I.intLe sym arg2 arg1)+  , (">", Op2 knownRepr Nothing $ \sym arg1 arg2 -> I.intLt sym arg2 arg1)+  -- Bitvector ops+  , ("bvnot", BVOp1 I.bvNotBits)+  , ("bvneg", BVOp1 I.bvNeg)+  , ("bvand", BVOp2 (Just LeftAssoc) I.bvAndBits)+  , ("bvor", BVOp2 (Just LeftAssoc) I.bvOrBits)+  , ("bvxor", BVOp2 (Just LeftAssoc) I.bvXorBits)+  , ("bvadd", BVOp2 (Just LeftAssoc) I.bvAdd)+  , ("bvsub", BVOp2 (Just LeftAssoc) I.bvSub)+  , ("bvmul", BVOp2 (Just LeftAssoc) I.bvMul)+  , ("bvudiv", BVOp2 Nothing I.bvUdiv)+  , ("bvurem", BVOp2 Nothing I.bvUrem)+  , ("bvshl", BVOp2 Nothing I.bvShl)+  , ("bvlshr", BVOp2 Nothing I.bvLshr)+  , ("bvsdiv", BVOp2 Nothing I.bvSdiv)+  , ("bvsrem", BVOp2 Nothing I.bvSrem)+  , ("bvashr", BVOp2 Nothing I.bvAshr)+  , ("bvult", BVComp2 I.bvUlt)+  , ("bvule", BVComp2 I.bvUle)+  , ("bvugt", BVComp2 I.bvUgt)+  , ("bvuge", BVComp2 I.bvUge)+  , ("bvslt", BVComp2 I.bvSlt)+  , ("bvsle", BVComp2 I.bvSle)+  , ("bvsgt", BVComp2 I.bvSgt)+  , ("bvsge", BVComp2 I.bvSge)+  ]++parseExpr ::+  forall sym . I.IsSymExprBuilder sym => sym -> SExp -> Processor sym (Some (I.SymExpr sym))+parseExpr sym sexp = case sexp of+  "true" -> return $ Some $ I.truePred sym+  "false" -> return $ Some $ I.falsePred sym+  _ | Just i <- parseIntSolverValue sexp ->+      liftIO $ Some <$> I.intLit sym i+    | Just (Pair w bv) <- parseBVLitHelper sexp+    , Just LeqProof <- testLeq (knownNat @1) w ->+      liftIO $ Some <$> I.bvLit sym w bv+  SAtom nm -> do+    env <- asks procLetEnv+    case HashMap.lookup nm env of+      Just expr -> return $ expr+      Nothing -> throwError ""+  SApp ["let", SApp bindings_sexp, body_sexp] -> do+    let_env <- HashMap.fromList <$> mapM+      (\case+        SApp [SAtom nm, expr_sexp] -> do+          Some expr <- parseExpr sym expr_sexp+          return (nm, Some expr)+        _ -> throwError "")+      bindings_sexp+    local (\prov_env -> prov_env { procLetEnv = HashMap.union let_env (procLetEnv prov_env) }) $+      parseExpr sym body_sexp+  SApp ["=", arg1, arg2] -> do+    Some arg1_expr <- parseExpr sym arg1+    Some arg2_expr <- parseExpr sym arg2+    case testEquality (I.exprType arg1_expr) (I.exprType arg2_expr) of+      Just Refl -> liftIO (Some <$> I.isEq sym arg1_expr arg2_expr)+      Nothing -> throwError ""+  SApp ["ite", arg1, arg2, arg3] -> do+    Some arg1_expr <- parseExpr sym arg1+    Some arg2_expr <- parseExpr sym arg2+    Some arg3_expr <- parseExpr sym arg3+    case I.exprType arg1_expr of+      I.BaseBoolRepr -> case testEquality (I.exprType arg2_expr) (I.exprType arg3_expr) of+        Just Refl -> liftIO (Some <$> I.baseTypeIte sym arg1_expr arg2_expr arg3_expr)+        Nothing -> throwError ""+      _ -> throwError ""+  SApp ["concat", arg1, arg2] -> do+    Some arg1_expr <- parseExpr sym arg1+    Some arg2_expr <- parseExpr sym arg2+    BVProof{} <- getBVProof arg1_expr+    BVProof{} <- getBVProof arg2_expr+    liftIO $ Some <$> I.bvConcat sym arg1_expr arg2_expr+  SApp ((SAtom operator) : operands) -> case HashMap.lookup operator (opTable @sym) of+    Just (Op1 arg_types fn) -> do+      args <- mapM (parseExpr sym) operands+      exprAssignment arg_types args >>= \case+        Ctx.Empty Ctx.:> arg1 ->+          liftIO (Some <$> fn sym arg1)+    Just (Op2 arg_types _ fn) -> do+      args <- mapM (parseExpr sym) operands+      exprAssignment arg_types args >>= \case+        Ctx.Empty Ctx.:> arg1 Ctx.:> arg2 ->+            liftIO (Some <$> fn sym arg1 arg2)+    Just (BVOp1 op) -> do+      Some arg_expr <- readOneArg sym operands+      BVProof{} <- getBVProof arg_expr+      liftIO $ Some <$> op sym arg_expr+    Just (BVOp2 _ op) -> do+      (Some arg1, Some arg2) <- readTwoArgs sym operands+      BVProof m <- prefixError "in arg 1: " $ getBVProof arg1+      BVProof n <- prefixError "in arg 2: " $ getBVProof arg2+      case testEquality m n of+        Just Refl -> liftIO (Some <$> op sym arg1 arg2)+        Nothing -> throwError $ printf "arguments to %s must be the same length, \+                                       \but arg 1 has length %s \+                                       \and arg 2 has length %s"+                                       operator+                                       (show m)+                                       (show n)+    Just (BVComp2 op) -> do+      (Some arg1, Some arg2) <- readTwoArgs sym operands+      BVProof m <- prefixError "in arg 1: " $ getBVProof arg1+      BVProof n <- prefixError "in arg 2: " $ getBVProof arg2+      case testEquality m n of+        Just Refl -> liftIO (Some <$> op sym arg1 arg2)+        Nothing -> throwError $ printf "arguments to %s must be the same length, \+                                       \but arg 1 has length %s \+                                       \and arg 2 has length %s"+                                       operator+                                       (show m)+                                       (show n)+    _ -> throwError ""+  _ -> throwError ""+-- | Verify a list of arguments has a single argument and+-- return it, else raise an error.+readOneArg ::+  I.IsSymExprBuilder sym+  => sym+  -> [SExp]+  -> Processor sym (Some (I.SymExpr sym))+readOneArg sym operands = do+  args <- mapM (parseExpr sym) operands+  case args of+    [arg] -> return arg+    _ -> throwError $ printf "expecting 1 argument, got %d" (length args)++-- | Verify a list of arguments has two arguments and return+-- it, else raise an error.+readTwoArgs ::+  I.IsSymExprBuilder sym+  => sym+  ->[SExp]+  -> Processor sym (Some (I.SymExpr sym), Some (I.SymExpr sym))+readTwoArgs sym operands = do+  args <- mapM (parseExpr sym) operands+  case args of+    [arg1, arg2] -> return (arg1, arg2)+    _ -> throwError $ printf "expecting 2 arguments, got %d" (length args)++exprAssignment ::+  forall sym ctx ex . (I.IsSymExprBuilder sym, I.IsExpr ex)+  => Ctx.Assignment BaseTypeRepr ctx+  -> [Some ex]+  -> Processor sym (Ctx.Assignment ex ctx)+exprAssignment tpAssns exs = do+  Some exsAsn <- return $ Ctx.fromList exs+  exsRepr <- return $ fmapFC I.exprType exsAsn+  case testEquality exsRepr tpAssns of+    Just Refl -> return exsAsn+    Nothing -> throwError $+      "Unexpected expression types for " -- ++ show exsAsn+      ++ "\nExpected: " ++ show tpAssns+      ++ "\nGot: " ++ show exsRepr++-- | Utility function for contextualizing errors. Prepends the given prefix+-- whenever an error is thrown.+prefixError :: (Monoid e, MonadError e m) => e -> m a -> m a+prefixError prefix act = catchError act (throwError . mappend prefix)++ ------------------------------------------------------------------------ -- Session @@ -891,6 +1275,32 @@         _ -> Nothing   getLimitedSolverResponse "get value" valRsp (sessionWriter s) (SMT2.getValue [e]) +-- | runGetAbducts s nm p n, returns n formulas (as strings) the disjunction of which entails p (along with all+--   the assertions in the context)+runGetAbducts :: SMTLib2Tweaks a+             => Session t a+             -> Int+             -> Text+             -> Term+             -> IO [String]+runGetAbducts s n nm p = +  if (n > 0) then do+    writeGetAbduct (sessionWriter s) nm p+    let valRsp = \x -> case x of+          -- SMT solver returns `(define-fun nm () Bool X)` where X is the abduct, we discard everything but the abduct+          AckSuccessSExp (SApp (_ : _ : _ : _ : abduct)) -> Just $ Data.String.unwords (map sExpToString abduct)+          _ -> Nothing+    -- get first abduct using the get-abduct command+    abd1 <- getLimitedSolverResponse "get abduct" valRsp (sessionWriter s) (SMT2.getAbduct nm p)+    if (n > 1) then do+      let rest = n - 1+      replicateM_ rest $ writeGetAbductNext (sessionWriter s)+      -- get the rest of the abducts using the get-abduct-next command+      abdRest <- forM [1..rest] $ \_ -> getLimitedSolverResponse "get abduct next" valRsp (sessionWriter s) (SMT2.getAbduct nm p)+      return (abd1:abdRest)+    else return [abd1]+  else return []+ -- | This function runs a check sat command runCheckSat :: forall b t a.                SMTLib2Tweaks b@@ -938,7 +1348,21 @@         cmd = getUnsatCoreCommand p     in getLimitedSolverResponse "unsat core" unsatCoreRsp s cmd +  smtAbductResult p s nm t =+    let abductRsp = \case+          AckSuccessSExp (SApp (_ : _ : _ : _ : abduct)) -> Just $ Data.String.unwords (map sExpToString abduct)+          _ -> Nothing+        cmd = getAbductCommand p nm t+    in getLimitedSolverResponse "get abduct" abductRsp s cmd +  smtAbductNextResult p s =+    let abductRsp = \case+          AckSuccessSExp (SApp (_ : _ : _ : _ : abduct)) -> Just $ Data.String.unwords (map sExpToString abduct)+          _ -> Nothing+        cmd = getAbductNextCommand p+    in getLimitedSolverResponse "get abduct next" abductRsp s cmd++ smtAckResult :: AcknowledgementAction t (Writer a) smtAckResult = AckAction $ getLimitedSolverResponse "get ack" $ \case                  AckSuccess -> Just ()@@ -1091,16 +1515,28 @@                  -> [B.BoolExpr t]                  -> IO () writeDefaultSMT2 a nm feat strictOpt sym h ps = do+  c <- defaultFileWriter a nm feat strictOpt sym h+  setProduceModels c True+  forM_ ps (SMTWriter.assume c)+  writeCheckSat c+  writeExit c++defaultFileWriter ::+  SMTLib2Tweaks a =>+  a ->+  String ->+  ProblemFeatures ->+  Maybe (CFG.ConfigOption I.BaseBoolType) ->+  B.ExprBuilder t st fs ->+  IO.Handle ->+  IO (WriterConn t (Writer a))+defaultFileWriter a nm feat strictOpt sym h = do   bindings <- B.getSymbolVarBimap sym   str <- Streams.encodeUtf8 =<< Streams.handleToOutputStream h   null_in <- Streams.nullInput   let cfg = I.getConfiguration sym   strictness <- parserStrictness strictOpt strictSMTParsing cfg-  c <- newWriter a str null_in nullAcknowledgementAction strictness nm True feat True bindings-  setProduceModels c True-  forM_ ps (SMTWriter.assume c)-  writeCheckSat c-  writeExit c+  newWriter a str null_in nullAcknowledgementAction strictness nm True feat True bindings  -- n.b. commonly used for the startSolverProcess method of the -- OnlineSolver class, so it's helpful for the type suffixes to align
src/What4/Protocol/SMTLib2/Parse.hs view
@@ -17,7 +17,7 @@   ( -- * CheckSatResponse     CheckSatResponse(..)   , readCheckSatResponse-    -- * GetModelResonse+    -- * GetModelResponse   , GetModelResponse   , readGetModelResponse   , ModelResponse(..)
src/What4/Protocol/SMTLib2/Response.hs view
@@ -71,6 +71,8 @@                  | AckSat                  | AckUnsat                  | AckUnknown+                 | AckInfeasible -- SyGuS response+                 | AckFail -- SyGuS response                  | RspName Text                  | RspVersion Text                  | RspErrBehavior Text@@ -144,10 +146,12 @@       parens p = AT.char '(' *> p <* AT.char ')'       errParser = parens $ lexeme (AT.string "error")                   *> (AckError <$> lexeme parseSMTLib2String)-      specific_success_response = check_sat_response <|> get_info_response+      specific_success_response = check_sat_response <|> check_synth_response <|> get_info_response       check_sat_response = (AckSat <$ AT.string "sat")                            <|> (AckUnsat <$ AT.string "unsat")                            <|> (AckUnknown <$ AT.string "unknown")+      check_synth_response = (AckInfeasible <$ AT.string "infeasible")+                             <|> (AckFail <$ AT.string "fail")       get_info_response = parens info_response       info_response = errBhvParser                       <|> nameParser
src/What4/Protocol/SMTLib2/Syntax.hs view
@@ -49,10 +49,19 @@   , assertNamed   , getUnsatAssumptions   , getUnsatCore+  , getAbduct+  , getAbductNext+    -- * SyGuS+  , synthFun+  , declareVar+  , constraint+  , checkSynth     -- * Logic   , Logic(..)   , qf_bv   , allSupported+  , allLogic+  , hornLogic     -- * Sort   , Sort(..)   , boolSort@@ -184,7 +193,16 @@ -- | Set the logic to all supported logics. allSupported :: Logic allSupported = Logic "ALL_SUPPORTED"+{-# DEPRECATED allSupported "Use allLogic instead" #-} +-- | Set the logic to all supported logics.+allLogic :: Logic+allLogic = Logic "ALL"++-- | Use the Horn logic+hornLogic :: Logic+hornLogic = Logic "HORN"+ ------------------------------------------------------------------------ -- Symbol @@ -290,7 +308,7 @@ or [x] = x or l = term_app "or" l --- | Disjunction of all terms+-- | Xor of all terms xor :: [Term] -> Term xor l@(_:_:_) = term_app "xor" l xor _ = error "xor expects two or more arguments."@@ -299,7 +317,7 @@ eq :: [Term] -> Term eq = chain_app "=" --- | Construct a chainable term with the givne relation+-- | Construct a chainable term with the given relation -- -- @pairwise_app p [x1, x2, ..., xn]@ is equivalent to -- \forall_{i,j} p x_i x_j@.@@ -480,7 +498,7 @@ -- value type `t2` that always returns `c`. -- -- This uses the non-standard SMTLIB2 syntax--- @((as const (Array t1 t2)) c)@ which is supported by CVC4 and Z3+-- @((as const (Array t1 t2)) c)@ which is supported by CVC4, CVC5, and Z3 -- (and perhaps others). arrayConst :: Sort -> Sort -> Term -> Term arrayConst itp rtp c =@@ -819,6 +837,37 @@  getUnsatCore :: Command getUnsatCore = Cmd "(get-unsat-core)"++-- | Get an abduct that entails the formula, and bind it to the name+getAbduct :: Text -> Term -> Command+getAbduct nm p = Cmd $ "(get-abduct " <> Builder.fromText nm <> " " <> renderTerm p <> ")"++-- | Get the next command, called after a get-abduct command+getAbductNext :: Command+getAbductNext = Cmd "(get-abduct-next)"++-- | Declare a SyGuS function to synthesize with the given name, arguments, and+-- return type.+synthFun :: Text -> [(Text, Sort)] -> Sort -> Command+synthFun f args ret_tp = Cmd $ app "synth-fun"+  [ Builder.fromText f+  , builder_list $ map (\(var, tp) -> app (Builder.fromText var) [unSort tp]) args+  , unSort ret_tp+  ]++-- | Declare a SyGuS variable with the given name and type.+declareVar :: Text -> Sort -> Command+declareVar v tp = Cmd $ app "declare-var" [Builder.fromText v, unSort tp]++-- | Add the SyGuS constraint to the current synthesis problem.+constraint :: Term -> Command+constraint p = Cmd $ app "constraint" [renderTerm p]++-- | Ask the SyGuS solver to find a solution for the synthesis problem+-- corresponding to the current functions-to-synthesize, variables and+-- constraints.+checkSynth :: Command+checkSynth = Cmd "(check-synth)\n"  -- | Get the values associated with the terms from the last call to @check-sat@. getValue :: [Term] -> Command
src/What4/Protocol/SMTWriter.hs view
@@ -69,6 +69,7 @@   , entryStackHeight   , pushEntryStack   , popEntryStack+  , cacheLookupFnNameBimap   , Command   , addCommand   , addCommandNoAck@@ -86,6 +87,10 @@   , ResponseStrictness(..)   , parserStrictness   , nullAcknowledgementAction+    -- * SyGuS+  , addSynthFun+  , addDeclareVar+  , addConstraint     -- * SMTWriter operations   , assume   , mkSMTTerm@@ -111,6 +116,8 @@ import           Control.Monad.ST import           Control.Monad.State.Strict import           Control.Monad.Trans.Maybe+import           Data.Bimap (Bimap)+import qualified Data.Bimap as Bimap import qualified Data.BitVector.Sized as BV import qualified Data.Bits as Bits import           Data.IORef@@ -820,6 +827,15 @@ cacheValueFn conn n lifetime value = cacheValue conn lifetime $ \entry ->   stToIO $ PH.insert (symFnCache entry) n value +cacheLookupFnNameBimap :: WriterConn t h -> [SomeExprSymFn t] -> IO (Bimap (SomeExprSymFn t) Text)+cacheLookupFnNameBimap conn fns = Bimap.fromList <$> mapM+  (\some_fn@(SomeExprSymFn fn) -> do+    maybe_smt_sym_fn <- cacheLookupFn conn $ symFnId fn+    case maybe_smt_sym_fn of+      Just (SMTSymFn nm _ _) -> return (some_fn, nm)+      Nothing -> fail $ "Could not find function in cache: " ++ show fn)+  fns+ -- | Run state with handle. withWriterState :: WriterConn t h -> State WriterState a -> IO a withWriterState c m = do@@ -867,12 +883,18 @@   --   later reporting unsatisfiable cores).   assertNamedCommand :: f h -> Term h -> Text -> Command h -  -- | Push 1 new scope+  -- | Generates command @(push 1)@ that opens the corresponding assertion frame   pushCommand   :: f h -> Command h -  -- | Pop 1 existing scope+  -- | Generates command @(pop 1)@ that closes the corresponding assertion frame   popCommand    :: f h -> Command h +  -- | Generates command @(push 2)@ that opens the corresponding assertion frame+  push2Command   :: f h -> Command h++  -- | Generates command @(pop 2)@ that closes the corresponding assertion frame, used for abduction+  pop2Command    :: f h -> Command h+   -- | Pop several scopes.   popManyCommands :: f h -> Int -> [Command h]   popManyCommands w n = replicate n (popCommand w)@@ -898,6 +920,12 @@   --   `checkCommand`.   getUnsatCoreCommand :: f h -> Command h +  -- | Ask the solver to return an abduct+  getAbductCommand :: f h -> Text -> Term h -> Command h++  -- | Ask the solver for the next abduct, used after a get-abduct command+  getAbductNextCommand :: f h -> Command h+   -- | Set an option/parameter.   setOptCommand :: f h -> Text -> Text -> Command h @@ -919,6 +947,23 @@                 -> Term h                 -> Command h +  -- | Declare a new SyGuS function to synthesize with the given name,+  -- arguments, and result type.+  synthFunCommand :: f h+                  -> Text+                  -> [(Text, Some TypeMap)]+                  -> TypeMap tp+                  -> Command h++  -- | Declare a new SyGuS universal variables with the given name and type.+  declareVarCommand :: f h+                    -> Text+                    -> TypeMap tp+                    -> Command h++  -- | Add a SyGuS formula to the set of synthesis constraints.+  constraintCommand :: f h -> Term h -> Command h+   -- | Declare a struct datatype if is has not been already given the number of   -- arguments in the struct.   declareStructDatatype :: WriterConn t h -> Ctx.Assignment TypeMap args -> IO ()@@ -1042,6 +1087,63 @@      assumeFormulaWithName conn p var      return var +addSynthFun ::+  SMTWriter h =>+  WriterConn t h ->+  ExprSymFn t args ret ->+  IO ()+addSynthFun conn fn =+  cacheLookupFn conn (symFnId fn) >>= \case+    Just{} ->+      fail $ "Internal error in SMTLIB exporter: function already declared."+        ++ show (symFnId fn) ++ " declared at "+        ++ show (plSourceLoc (symFnLoc fn)) ++ "."+    Nothing -> case symFnInfo fn of+      UninterpFnInfo arg_types ret_type -> do+        nm <- getSymbolName conn (FnSymbolBinding fn)+        let fn_source = fnSource (symFnName fn) (symFnLoc fn)+        smt_arg_types <- traverseFC (evalFirstClassTypeRepr conn fn_source) arg_types+        checkArgumentTypes conn smt_arg_types+        smt_ret_type <- evalFirstClassTypeRepr conn fn_source ret_type+        traverseFC_ (declareTypes conn) smt_arg_types+        declareTypes conn smt_ret_type+        smt_args <- mapM+          (\(Some tp) -> do+            var <- withWriterState conn $ freshVarName+            return (var, Some tp))+          (toListFC Some smt_arg_types)+        addCommand conn $ synthFunCommand conn nm smt_args smt_ret_type+        cacheValueFn conn (symFnId fn) DeleteNever $! SMTSymFn nm smt_arg_types smt_ret_type+      DefinedFnInfo{} ->+        fail $ "Internal error in SMTLIB exporter: defined functions cannot be synthesized."+      MatlabSolverFnInfo{} ->+        fail $ "Internal error in SMTLIB exporter: MatlabSolver functions cannot be synthesized."++addDeclareVar ::+  SMTWriter h =>+  WriterConn t h ->+  ExprBoundVar t tp ->+  IO ()+addDeclareVar conn var =+  cacheLookupExpr conn (bvarId var) >>= \case+    Just{} ->+      fail $ "Internal error in SMTLIB exporter: variable already declared."+        ++ show (bvarId var) ++ " declared at "+        ++ show (plSourceLoc (bvarLoc var)) ++ "."+    Nothing -> do+      nm <- getSymbolName conn (VarSymbolBinding var)+      let fn_source = fnSource (bvarName var) (bvarLoc var)+      smt_type <- evalFirstClassTypeRepr conn fn_source $ bvarType var+      declareTypes conn smt_type+      addCommand conn $ declareVarCommand conn nm smt_type+      cacheValueExpr conn (bvarId var) DeleteNever $! SMTName smt_type nm++addConstraint :: SMTWriter h => WriterConn t h -> BoolExpr t -> IO ()+addConstraint conn p = do+  f <- mkFormula conn p+  updateProgramLoc conn (exprLoc p)+  addCommand conn $ constraintCommand conn f+ -- | Perform any necessary declarations to ensure that the mentioned type map --   sorts exist in the solver environment. declareTypes ::@@ -2981,6 +3083,12 @@   -- | Parse a list of names of assumptions that form an unsatisfiable core.   --   These correspond to previously-named assertions.   smtUnsatCoreResult :: f h -> WriterConn t h -> IO [Text]++  -- | Parse an abduct returned by the get-abduct command+  smtAbductResult :: f h -> WriterConn t h -> Text -> Term h -> IO String++  -- | Parse an abduct returned by the get-abduct-next command+  smtAbductNextResult :: f h -> WriterConn t h -> IO String    -- | Parse a list of names of assumptions that form an unsatisfiable core.   --   The boolean indicates the polarity of the atom: true for an ordinary
src/What4/Protocol/VerilogWriter.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} {- Module           : What4.Protocol.VerilogWriter.AST Copyright        : (c) Galois, Inc 2020
+ src/What4/Serialize/FastSExpr.hs view
@@ -0,0 +1,184 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+-- | This module implements a specialized s-expression parser+--+-- The parser in s-cargot is very general, but that also makes it a bit+-- inefficient.  This module implements a drop-in replacement parser for the one+-- in What4.Serialize.Parser using megaparsec.  It is completely specialized to+-- the types in this library.+module What4.Serialize.FastSExpr (+  parseSExpr+  ) where++import           Control.Applicative+import qualified Control.Monad.Fail as MF+import qualified Data.Parameterized.NatRepr as PN+import           Data.Parameterized.Some ( Some(..) )+import           Data.Ratio ( (%) )+import qualified Data.SCargot.Repr.WellFormed as SC+import qualified Data.Set as Set+import qualified Data.Text as T+import qualified LibBF as BF+import           Numeric.Natural ( Natural )+import qualified Text.Megaparsec as TM+import qualified Text.Megaparsec.Char as TMC+import qualified Text.Megaparsec.Char.Lexer as TMCL+import qualified What4.BaseTypes as WT+import qualified What4.Serialize.SETokens as WST++-- | Parse 'T.Text' into the well-formed s-expression type from s-cargot.+parseSExpr :: T.Text -> Either String (SC.WellFormedSExpr WST.Atom)+parseSExpr t =+  case TM.runParser (ws >> parse) "<input>" t of+    Left errBundle -> Left (TM.errorBundlePretty errBundle)+    Right a -> Right a++data What4ParseError = ErrorParsingHexFloat String+                     | InvalidExponentOrSignificandSize Natural Natural+  deriving (Show, Eq, Ord)++instance TM.ShowErrorComponent What4ParseError where+  showErrorComponent e =+    case e of+      ErrorParsingHexFloat hf -> "Error parsing hex float literal: " ++ hf+      InvalidExponentOrSignificandSize ex s ->+        concat [ "Invalid exponent or significand size: exponent size = "+               , show ex+               , ", significand size = "+               , show s+               ]++type Parser a = TM.Parsec What4ParseError T.Text a++parse :: Parser (SC.WellFormedSExpr WST.Atom)+parse = parseList <|> (SC.WFSAtom <$> lexeme parseAtom)++parseList :: Parser (SC.WellFormedSExpr WST.Atom)+parseList = do+  _ <- lexeme (TMC.char '(')+  items <- TM.many parse+  _ <- lexeme (TMC.char ')')+  return (SC.WFSList items)++parseId :: Parser T.Text+parseId = T.pack <$> ((:) <$> first <*> TM.many rest)+  where+    w4symbol c =  c == '@'+               || c == '+'+               || c == '-'+               || c == '='+               || c == '<'+               || c == '>'+               || c == '_'+               || c == '.'+    first = TMC.letterChar <|> TM.satisfy w4symbol+    rest = TMC.alphaNumChar <|> TM.satisfy w4symbol++parseNat :: Parser Natural+parseNat = do+  _ <- TMC.string "#u"+  TMCL.decimal++parseInt :: Parser Integer+parseInt = TMCL.decimal <|> (negate <$> (TMC.char '-' *> TMCL.decimal))++parseReal :: Parser Rational+parseReal = do+  _ <- TMC.string "#r"+  n <- TMCL.decimal+  _ <- TMC.char '/'+  d <- TMCL.decimal+  return (n % d)++parseBV :: Parser (Int, Integer)+parseBV = do+  _ <- TMC.char '#'+  t <- TM.anySingle+  case t of+    'b' -> parseBin 0 0+    'x' -> parseHex+    _ -> MF.fail ("Invalid bitvector class: " ++ show t)+  where+    parseBin :: Int -> Integer -> Parser (Int, Integer)+    parseBin !nBits !value= do+      mb <- TM.optional TMC.binDigitChar+      case mb of+        Nothing -> return (nBits, value)+        Just bitChar -> parseBin (nBits + 1) (value * 2 + if bitChar == '1' then 1 else 0)+    parseHex :: Parser (Int, Integer)+    parseHex = do+      digits <- TM.some TMC.hexDigitChar+      return (length digits * 4, read ("0x" ++ digits))++parseBool :: Parser Bool+parseBool = do+  _ <- TMC.char '#'+  TM.try (TMC.string "true" *> return True) <|> (TMC.string "false" *> return False)++parseStrInfo :: Parser (Some WT.StringInfoRepr)+parseStrInfo = TM.try (TMC.string "#char16" >> return (Some WT.Char16Repr))+           <|> TM.try (TMC.string "#char8" >> return (Some WT.Char8Repr))+           <|> return (Some WT.UnicodeRepr)++parseStr :: Parser (Some WT.StringInfoRepr, T.Text)+parseStr = do+  prefix <- parseStrInfo+  _ <- TMC.char '"'+  str <- concat <$> TM.many (parseEscaped <|> TM.some (TM.noneOf ('"':"\\")))+  _ <- TMC.char '"'+  return (prefix, T.pack str)+  where+    parseEscaped = do+      _ <- TMC.char '\\'+      c <- TM.anySingle+      return ['\\', c]++parseFloat :: Parser (Some WT.FloatPrecisionRepr, BF.BigFloat)+parseFloat = do+  _ <- TMC.string "#f#"+  -- We printed the nat reprs out in decimal+  eb :: Natural+     <- TMCL.decimal+  _ <- TMC.char '#'+  sb :: Natural+     <- TMCL.decimal+  _ <- TMC.char '#'++  -- The float value itself is printed out as a hex literal+  hexDigits <- TM.some TMC.hexDigitChar++  Some ebRepr <- return (PN.mkNatRepr eb)+  Some sbRepr <- return (PN.mkNatRepr sb)+  case (PN.testLeq (PN.knownNat @2) ebRepr, PN.testLeq (PN.knownNat @2) sbRepr) of+    (Just PN.LeqProof, Just PN.LeqProof) -> do+      let rep = WT.FloatingPointPrecisionRepr ebRepr sbRepr++      -- We know our format: it is determined by the exponent bits (eb) and the+      -- significand bits (sb) parsed above+      let fmt = BF.precBits (fromIntegral sb) <> BF.expBits (fromIntegral eb)+      let (bf, status) = BF.bfFromString 16 fmt hexDigits+      case status of+        BF.Ok -> return (Some rep, bf)+        _ -> TM.fancyFailure (Set.singleton (TM.ErrorCustom (ErrorParsingHexFloat hexDigits)))+    _ -> TM.fancyFailure (Set.singleton (TM.ErrorCustom (InvalidExponentOrSignificandSize eb sb)))+++parseAtom :: Parser WST.Atom+parseAtom = TM.try (uncurry WST.ABV <$> parseBV)+        <|> TM.try (WST.ABool <$> parseBool)+        <|> TM.try (WST.AInt <$> parseInt)+        <|> TM.try (WST.AId <$> parseId)+        <|> TM.try (WST.ANat <$> parseNat)+        <|> TM.try (WST.AReal <$> parseReal)+        <|> TM.try (uncurry WST.AStr <$> parseStr)+        <|> TM.try (uncurry WST.AFloat <$> parseFloat)++ws :: Parser ()+ws = TMCL.space TMC.space1 (TMCL.skipLineComment (T.pack ";")) empty++lexeme :: Parser a -> Parser a+lexeme = TMCL.lexeme ws
+ src/What4/Serialize/Log.hs view
@@ -0,0 +1,436 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE NondecreasingIndentation #-}+-- | Description: Log msgs via a synchronized channel+--+-- Log msgs via a synchronized channel.+--+-- With inspiration from the @monad-logger@ package.+--+-- See examples in 'SemMC.Log.Tests'.+--+-- WARNING: loggers that automatically infer the call stack (via+-- `Ghc.HasCallStack`) are not composable, in that they infer a call+-- stack at their call site. So, if you use one to build up another+-- log function, then that derived log function will infer bogus call+-- sites! Of course, it's pretty easy to write+--+--     writeLogEvent logCfg level msg+--+-- when defining a new logger, so not a big deal, just something to+-- watch out for.+module What4.Serialize.Log (+  -- * Misc+  LogLevel(..),+  LogEvent(..),+  LogMsg,+  Ghc.HasCallStack,+  -- * Implicit param logger interface+  HasLogCfg,+  logIO,+  logTrace,+  withLogCfg,+  getLogCfg,+  -- * Explicit parameter logger interface+  logIOWith,+  logEndWith,+  writeLogEvent,+  -- * Monadic logger interface+  MonadHasLogCfg(..),+  logM,+  -- * Configuration+  LogCfg,+  mkLogCfg,+  mkNonLogCfg,+  withLogging,+  -- * Log consumers+  stdErrLogEventConsumer,+  fileLogEventConsumer,+  tmpFileLogEventConsumer,+  -- * Log formatting and consumption (useful for 3rd-party consumers)+  prettyLogEvent,+  consumeUntilEnd,+  -- * Named threads+  named,+  namedIO,+  namedM+  ) where++import qualified GHC.Stack as Ghc++import qualified Control.Concurrent as Cc+import qualified Control.Exception as Cc+import           Control.Monad (when)++import qualified Data.Time.Clock as T+import qualified Data.Time.Format as T++import qualified System.IO as IO+import qualified System.IO.Unsafe as IO++import qualified UnliftIO as U++import qualified Control.Concurrent.STM as Stm+import qualified Control.Concurrent.BoundedChan as BC+import           Control.Monad.IO.Class ( MonadIO, liftIO )+import           Data.Map.Strict ( Map )+import qualified Data.Map.Strict as Map+import           System.Directory ( createDirectoryIfMissing, getTemporaryDirectory )+import           Text.Printf ( printf )++import Debug.Trace++----------------------------------------------------------------+-- * API++-- | Log levels, in increasing severity/precedence order.+data LogLevel = Debug -- ^ Fine details+              | Info  -- ^ Tracking progress+              | Warn  -- ^ Something notable or suspicious+              | Error -- ^ Something bad+              deriving (Show, Eq, Ord, Read)++type LogMsg = String++----------------------------------------------------------------+-- ** Implicit param logger interface++-- | Access to the log config.+--+-- Users should prefer 'withLogCfg' to binding the implicit param. The+-- implicit param is an implementation detail, and we could change the+-- implementation later, e.g. to use the @reflection@ package.+--+-- We use an implicit param to avoid having to change all code in 'IO'+-- that wants to log to be in 'MonadHasLogCfg' and 'MonadIO' classes.+--+-- An even more convenient but more \"unsafe\" implementation would+-- store the 'LogCfg' in a global, 'unsafePerformIO'd 'IORef'+-- (cf. @uniqueSource@ in 'Data.Unique').+type HasLogCfg = (?logCfg :: LogCfg)++-- | Satisfy a 'HasLogCfg' constraint.+--+-- Users can call this function instead of using @ImplicitParams@+-- themselves.+withLogCfg :: LogCfg -> (HasLogCfg => a) -> a+withLogCfg logCfg x = let ?logCfg = logCfg in x++-- | Recover the log config.+--+-- Useful for going between implicit and monadic interfaces. E.g.+--+-- > flip runReaderT getLogCfg ...+getLogCfg :: HasLogCfg => LogCfg+getLogCfg = ?logCfg++-- | Log in a 'MonadIO'.+--+-- If you want the name of function that called 'log' to be included+-- in the output, then you need to add a 'Ghc.HasCallStack' constraint+-- to it as well (see 'LogC'). Otherwise, one of two things will happen:+--+-- - if no enclosing function has a 'Ghc.HasCallStack' constraint,+--   then '???' will be used for the enclosing function name.+--+-- - if at least one enclosing function has a 'Ghc.HasCallStack'+--   constraint, then the name of the *closest* enclosing function+--   with that constraint will be used for the enclosing function+--   name. So, for example, if you define @outer@ by+--+--   > outer :: (MonadHasLogCfg m, Ghc.HasCallStack) => m Int+--   > outer = inner+--   >   where+--   >     inner = do+--   >       log Debug "Inside 'inner' ..."+--   >       return 42+--+--   then the call to 'log' in @inner@ will have \"outer\" as the+--   enclosing function name.+logIO :: (HasLogCfg, Ghc.HasCallStack, MonadIO m)+      => LogLevel -> LogMsg -> m ()+logIO level msg = do+  liftIO $ writeLogEvent ?logCfg Ghc.callStack level msg++-- | 'logIO' with an explicit config+logIOWith :: (Ghc.HasCallStack, MonadIO m) => LogCfg -> LogLevel -> LogMsg -> m ()+logIOWith cfg level msg =+  liftIO $ writeLogEvent cfg Ghc.callStack level msg++-- | Log in pure code using 'unsafePerformIO', like 'Debug.Trace'.+--+-- See 'logIO'.+logTrace :: (HasLogCfg, Ghc.HasCallStack) => LogLevel -> LogMsg -> a -> a+logTrace level msg x = IO.unsafePerformIO $ do+  writeLogEvent ?logCfg Ghc.callStack level msg+  return x+{-# NOINLINE logTrace #-}++----------------------------------------------------------------+-- ** Monadic logger interface++-- | Monads with logger configuration.+class MonadHasLogCfg m where+  getLogCfgM :: m LogCfg++-- | Log in a 'MonadHasLogCfg'.+--+-- See 'logIO'.+logM :: (MonadHasLogCfg m, Ghc.HasCallStack, MonadIO m)+     => LogLevel -> LogMsg -> m ()+logM level msg = do+  logCfg <- getLogCfgM+  liftIO $ writeLogEvent logCfg Ghc.callStack level msg++-- | Signal to the log consumer that there are no more log messages and+-- terminate the log consumer.  This is useful for cases where the logger is+-- running in a separate thread and the parent thread wants to wait until the+-- logger has finished logging and has successfully flushed all log messages+-- before terminating it.+logEndWith :: LogCfg -> IO ()+logEndWith cfg = case lcChan cfg of+                   Just c -> BC.writeChan c Nothing+                   Nothing -> return ()++----------------------------------------------------------------+-- ** Initialization++-- | Initialize a 'LogCfg'.+--+-- The first argument is the human friendly name to assign to the+-- current thread. Since logging should be configured as soon as+-- possible on startup, \"main\" is probably the right name.+--+-- See 'asyncNamed' for naming other threads.+--+-- Need to start a log event consumer in another thread,+-- e.g. 'stdErrLogEventConsumer', if you want anything to happen with+-- the log events.+mkLogCfg :: String -> IO LogCfg+mkLogCfg threadName = do+  chan <- BC.newBoundedChan 100+  threadMap <- do+    tid <- show <$> Cc.myThreadId+    return $ Map.fromList [ (tid, threadName) ]+  threadMapVar <- Stm.newTVarIO threadMap+  return $ LogCfg { lcChan = Just chan+                  , lcThreadMap = threadMapVar }+++-- | Initialize a 'LogCfg' that does no logging.+--+-- This can be used as a LogCfg when no logging is to be performed.+-- Runtime overhead is smaller when this configuration is specified at+-- compile time.+mkNonLogCfg :: IO LogCfg+mkNonLogCfg = do tmVar <- Stm.newTVarIO Map.empty+                 return LogCfg { lcChan = Nothing+                               , lcThreadMap = tmVar+                               }+++-- | Run an action with the given log event consumer.+--+-- In particular this provides an easy way to run one-off computations+-- that assume logging, e.g. in GHCi. Spawns the log even consumer+-- before running the action and cleans up the log event consumer+-- afterwards.+withLogging :: (U.MonadUnliftIO m, MonadIO m)+            => String -> (LogCfg -> IO ()) -> (HasLogCfg => m a) -> m a+withLogging threadName logEventConsumer action = do+  cfg <- liftIO $ mkLogCfg threadName+  U.withAsync (liftIO $ logEventConsumer cfg) $ \a -> do+  x <- withLogCfg cfg action+  liftIO $ logEndWith cfg+  U.wait a+  return x++----------------------------------------------------------------+-- ** Log event consumers++-- | Consume a log channel until it receives a shutdown message+-- (i.e. a 'Nothing').+--+-- Only messages that satisfy the predicate will be passed to the+-- continuation. For example, using @const True@ will process all log+-- messages, and using @(>= Info) . leLevel@ will only process+-- messsages with 'LogLevel' equal to 'Info' or higher, ignoring+-- 'Debug' level messages.+consumeUntilEnd ::+  (LogEvent -> Bool) -> (LogEvent -> IO ()) -> LogCfg -> IO ()+consumeUntilEnd keepEvent k cfg =+  case lcChan cfg of+    Nothing -> return ()+    Just c -> do+      mevent <- BC.readChan c+      case mevent of+        Just event -> do when (keepEvent event) $ k event+                         consumeUntilEnd keepEvent k cfg+        _ -> return ()++-- | A log event consumer that prints formatted log events to stderr.+stdErrLogEventConsumer :: (LogEvent -> Bool) -> LogCfg -> IO ()+stdErrLogEventConsumer keepEvent =+  consumeUntilEnd keepEvent $ \e -> do+    -- Use 'traceIO' because it seems to be atomic in practice,+    -- avoiding problems with interleaving output from other sources.+    traceIO (prettyLogEvent e)+    IO.hFlush IO.stderr -- Probably unnecessary.++-- | A logger that writes to a user-specified file+--+-- Note that logs are opened in the 'w' mode (i.e., overwrite).  Callers should+-- preserve old log files if they really want.+fileLogEventConsumer :: FilePath -> (LogEvent -> Bool) -> LogCfg -> IO ()+fileLogEventConsumer fp keepEvent cfg = IO.withFile fp IO.WriteMode $ \h -> do+  let k e = IO.hPutStrLn h (prettyLogEvent e) >> IO.hFlush h+  consumeUntilEnd keepEvent k cfg++-- | A log event consumer that writes formatted log events to a tmp+-- file.+tmpFileLogEventConsumer :: (LogEvent -> Bool) -> LogCfg -> IO ()+tmpFileLogEventConsumer keepEvent cfg = do+  tmpdir <- (++ "/brittle") <$> getTemporaryDirectory+  createDirectoryIfMissing True tmpdir+  (tmpFilePath, tmpFile) <- IO.openTempFile tmpdir "log.txt"+  printf "\n\nWriting logs to %s\n\n" tmpFilePath+  let k e = IO.hPutStrLn tmpFile (prettyLogEvent e) >> IO.hFlush tmpFile+  consumeUntilEnd keepEvent k cfg++----------------------------------------------------------------+-- ** Named threads++-- | Run an IO action with a human friendly thread name.+--+-- Any existing thread name will be restored when the action finishes.+named :: (U.MonadUnliftIO m, MonadIO m) => LogCfg -> String -> m a -> m a+named cfg threadName action = do+  actionIO <- U.toIO action+  liftIO $ do+    tid <- show <$> Cc.myThreadId+    mOldName <- Map.lookup tid <$> Stm.readTVarIO (lcThreadMap cfg)+    Cc.bracket_ (insert tid) (remove tid mOldName) actionIO+  where+    modify = Stm.atomically . Stm.modifyTVar' (lcThreadMap cfg)++    insert tid = modify $ Map.insert tid threadName++    remove tid Nothing        = modify $ Map.delete tid+    remove tid (Just oldName) = modify $ Map.insert tid oldName++-- | Version of 'named' for implicit log cfg.+namedIO :: (HasLogCfg, U.MonadUnliftIO m, MonadIO m)+        => String -> m a -> m a+namedIO threadName action = named ?logCfg threadName action++-- | Version of 'named' for 'MonadHasLogCfg' monads.+namedM :: (MonadHasLogCfg m, U.MonadUnliftIO m, MonadIO m)+       => String -> m a -> m a+namedM threadName action = do+  cfg <- getLogCfgM+  named cfg threadName action++----------------------------------------------------------------+-- * Internals++-- | Stored as 'String' because 'Control.Concurrent.ThreadId' docs say+-- a thread can't be GC'd as long as someone maintains a reference to+-- its 'ThreadId'!!!+type ThreadId = String++-- | A log event.+--+-- Can be converted to a string later, or thrown away.+data LogEvent = LogEvent+  { leCallSite :: (Maybe String, Ghc.SrcLoc)+    -- ^ The @Maybe String@ is the name of the enclosing function in+    -- which the logging function was called. Not always available,+    -- since it depends on the enclosing function having a+    -- 'Ghc.HasCallStack' constraint.+  , leLevel    :: LogLevel+  , leMsg      :: LogMsg+  , leThreadId :: ThreadId+    -- ^ ID of thread that generated the event.+  , leTime     :: T.UTCTime+  }++-- | Logging configuration.+data LogCfg = LogCfg+  { lcChan :: Maybe (BC.BoundedChan (Maybe LogEvent))+  , lcThreadMap :: Stm.TVar (Map ThreadId String)+    -- ^ User friendly names for threads. See 'asyncNamed'.++  -- Idea: add a predicate on log events that is used to discard log+  -- events that e.g. aren't of a high enough precedence+  -- level. E.g. only keep events of level 'Warn' or above:+  --+  -- > lcPred le = leLevel le >= Warn+  --+  -- , lcPred :: LogEvent -> Bool+  }++-- | Format a log event.+prettyLogEvent :: LogEvent -> String+prettyLogEvent le =+  printf "[%s][%s][%s][%s]\n%s"+    (show $ leLevel le) time location (leThreadId le) (leMsg le)+  where+    time :: String+    time = T.formatTime T.defaultTimeLocale "%T" (leTime le)+    location :: String+    location = printf "%s:%s"+      (prettyFun maybeFun) (Ghc.prettySrcLoc srcLoc)+    (maybeFun, srcLoc) = leCallSite le+    prettyFun Nothing = "???"+    prettyFun (Just fun) = fun++prettyThreadId :: LogCfg -> ThreadId -> IO ThreadId+prettyThreadId cfg tid = do+  mThreadName <- Map.lookup tid <$> Stm.readTVarIO (lcThreadMap cfg)+  return $ printf "%s (%s)" (maybe "???" id mThreadName) tid++-- | Write a 'LogEvent' to the underlying channel.+--+-- This is a low-level function. See 'logIO', 'logM', and 'logTrace'+-- for a high-level interface that supplies the 'LogCfg' and+-- 'Ghc.CallStack' parameters automatically.+--+-- However, those functions can't be used to build up custom loggers,+-- since they infer call stack information automatically. If you want+-- to define a custom logger (even something simple like+--+-- > debug msg = logM Debug msg+--+-- ) then use 'writeLogEvent'.+writeLogEvent :: LogCfg -> Ghc.CallStack -> LogLevel -> LogMsg -> IO ()+writeLogEvent cfg cs level msg = do+  tid <- show <$> Cc.myThreadId+  ptid <- prettyThreadId cfg tid+  time <- T.getCurrentTime+  case lcChan cfg of+    Nothing -> return ()+    Just c -> BC.writeChan c (Just (event ptid time))+  where+    event tid time = LogEvent+      { leCallSite = callSite+      , leLevel = level+      , leMsg = msg+      , leThreadId = tid+      , leTime = time+      }+    -- | The call stack has the most recent call first. Assuming+    -- 'writeLogEvent' is always called in a logging function with a+    -- 'Ghc.HasCallStack' constraint, the call stack will be non-empty+    -- -- i.e. @topSrcLoc@ will be defined -- but there may not be a+    -- lower frame corresponding to the context in which the logging+    -- function was called. To get a lower frame, some enclosing+    -- function needs a 'Ghc.HasCallStack' constraint itself.+    --+    -- And only functions with 'Ghc.HasCallStack' will get frames. See+    -- discussion at 'log'.+    callSite = case Ghc.getCallStack cs of+                 (_,topSrcLoc):rest -> case rest of+                   []                 -> (Nothing,           topSrcLoc)+                   (enclosingFun,_):_ -> (Just enclosingFun, topSrcLoc)+                 [] -> error "Do we ever not have a call site?"
+ src/What4/Serialize/Normalize.hs view
@@ -0,0 +1,162 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE MultiWayIf #-}++-- | Normalization and equivalence checking for expressions+module What4.Serialize.Normalize+  ( normSymFn+  , normExpr+  , testEquivSymFn+  , testEquivExpr+  , ExprEquivResult(..)+  ) where++import qualified Data.Parameterized.Context as Ctx+import qualified Data.Parameterized.TraversableFC as FC++import qualified What4.Interface as S+import qualified What4.Expr as S+import qualified What4.Expr.Builder as B+import qualified What4.Expr.WeightedSum as WSum+import           Data.Parameterized.Classes++-- | Apply some normalizations to make function call arguments more readable.  Examples include:+--+--  * Avoid wrapping single literals in a 'B.SemiRingLiteral' and just represent them as a bare integer literals+--  * Attempt to reduce function calls with constant arguments where possible+normSymFn :: forall sym st fs t args ret. sym ~ B.ExprBuilder t st fs+          => sym+          -> B.ExprSymFn t args ret+          -> Ctx.Assignment (S.Expr t) args+          -> IO (S.Expr t ret)+normSymFn sym symFn argEs = case B.symFnInfo symFn of+  B.DefinedFnInfo argBVs expr _ -> do+    argEs' <- FC.traverseFC (normExpr sym) argEs+    expr' <- B.evalBoundVars sym expr argBVs argEs'+    normExpr sym expr'+  _ -> S.applySymFn sym symFn argEs+++normExpr :: forall sym st fs t tp+          . sym ~ B.ExprBuilder t st fs+         => sym+         -> B.Expr t tp -> IO (B.Expr t tp)+normExpr sym e = go e+  where go :: B.Expr t tp -> IO (B.Expr t tp)+        go (B.SemiRingLiteral S.SemiRingIntegerRepr val _) = S.intLit sym val+        go (B.AppExpr appExpr) = normAppExpr sym appExpr+        go x@(B.NonceAppExpr nae) =+          case B.nonceExprApp nae of+            B.FnApp fn args -> normSymFn sym fn args+            _ -> return x+        go x = return x++-- | Normalize an expression by passing it back through the builder+--+-- NOTE: We may want to audit the cases here for completeness+normAppExpr :: forall sym st fs t tp+             . sym ~ S.ExprBuilder t st fs+            => sym+            -> S.AppExpr t tp+            -> IO (S.Expr t tp)+normAppExpr sym ae = do+  e' <- go (S.appExprApp ae)+  B.sbMakeExpr sym e'+  where norm2 :: forall tp' tp'' tp'''+               . (S.Expr t tp' -> S.Expr t tp'' -> IO (S.Expr t tp'''))+              -> S.Expr t tp' -> S.Expr t tp'' -> IO (S.Expr t tp''')+        norm2 f e1 e2 = do+          e1' <- normExpr sym e1+          e2' <- normExpr sym e2+          f e1' e2'++        go :: forall tp'. S.App (S.Expr t) tp' -> IO (S.App (S.Expr t) tp')+        go (S.BaseIte _ _ test then_ else_) = do+          test' <- normExpr sym test+          then' <- normExpr sym then_+          else' <- normExpr sym else_+          Just sm' <- B.asApp <$> S.baseTypeIte sym test' then' else'+          return sm'+        go x@(S.SemiRingSum sm) =+          case WSum.sumRepr sm of+            S.SemiRingIntegerRepr -> do+              let+                smul si i = do+                    i' <- normExpr sym i+                    si' <- S.intLit sym si+                    S.intMul sym si' i'+              Just sm' <- B.asApp <$> WSum.evalM (norm2 $ S.intAdd sym) smul (S.intLit sym) sm+              return sm'+            _ -> return x+        go x@(S.SemiRingProd pd) =+          case WSum.prodRepr pd of+            S.SemiRingIntegerRepr -> do+                maybeS <- WSum.prodEvalM (norm2 $ S.intMul sym) return pd+                case maybeS of+                  Just s | Just sm' <- B.asApp s -> return sm'+                  _ -> return x+            _ -> return x+        go x@(S.SemiRingLe sr e1 e2) = do+          case sr of+            S.OrderedSemiRingIntegerRepr -> do+              Just sm' <- B.asApp <$> (norm2 $ S.intLe sym) e1 e2+              return sm'+            _ -> return x+        go x = return x++++data ExprEquivResult = ExprEquivalent | ExprNormEquivalent | ExprUnequal++testEquivExpr :: forall sym st fs t tp tp'. sym ~ S.ExprBuilder t st fs => sym -> B.Expr t tp -> B.Expr t tp' -> IO (ExprEquivResult)+testEquivExpr sym e1 e2 = case testEquality e1 e2 of+  Just Refl -> return ExprEquivalent+  _ -> do+    e1' <- normExpr sym e1+    e2' <- normExpr sym e2+    case testEquality e1' e2' of+      Just Refl -> return ExprNormEquivalent+      _ -> return ExprUnequal++testEquivSymFn :: forall sym st fs t args ret args' ret'. sym ~ S.ExprBuilder t st fs => sym -> S.SymFn sym args ret -> S.SymFn sym args' ret' -> IO (ExprEquivResult)+testEquivSymFn sym fn1 fn2 =+  let+    argTypes1 = S.fnArgTypes fn1+    argTypes2 = S.fnArgTypes fn2+    retType1 = S.fnReturnType fn1+    retType2 = S.fnReturnType fn2+  in if | Just Refl <- testEquality argTypes1 argTypes2+        , Just Refl <- testEquality retType1 retType2+        , B.symFnName fn1 == B.symFnName fn2 ->+          case (S.symFnInfo fn1, S.symFnInfo fn2) of+           (S.DefinedFnInfo argBVs1 efn1 _, S.DefinedFnInfo argBVs2 efn2 _) -> do+             args <- FC.traverseFC (\bv -> S.freshConstant sym (S.bvarName bv) (B.bvarType bv)) argBVs1+             expr1 <- B.evalBoundVars sym efn1 argBVs1 args+             expr2 <- B.evalBoundVars sym efn2 argBVs2 args+             case testEquality expr1 expr2 of+               Just Refl -> return ExprEquivalent+               Nothing -> do+                 expr1' <- normExpr sym expr1+                 expr2' <- normExpr sym expr2+                 case testEquality expr1' expr2' of+                   Just Refl -> return ExprNormEquivalent+                   Nothing -> return ExprUnequal+           (S.UninterpFnInfo _ _, S.UninterpFnInfo _ _) -> return ExprEquivalent+           (S.MatlabSolverFnInfo _ _ _, _) -> fail "Unsupported function type for equivalence check."+           (_, S.MatlabSolverFnInfo _ _ _) -> fail "Unsupported function type for equivalence check."+           (_, _) -> return ExprUnequal+        | otherwise -> return ExprUnequal
+ src/What4/Serialize/Parser.hs view
@@ -0,0 +1,1070 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE MultiWayIf #-}++-- | A parser for an s-expression representation of what4 expressions+module What4.Serialize.Parser+  ( deserializeExpr+  , deserializeExprWithConfig+  , deserializeSymFn+  , deserializeSymFnWithConfig+  , deserializeBaseType+  , readBaseTypes+  , Atom(..)+  , S.WellFormedSExpr(..)+  , Config(..)+  , defaultConfig+  , SomeSymFn(..)+  , type SExpr+  , parseSExpr+  , printSExpr+  ) where++import qualified Control.Monad.Except as E+import           Control.Monad.IO.Class ( liftIO )+import qualified Control.Monad.Reader as R+import qualified Data.BitVector.Sized as BV+import qualified Data.Foldable as F+import qualified Data.HashMap.Lazy as HM+import           Data.Kind+import qualified Data.SCargot.Repr.WellFormed as S++import           Data.Text ( Text )+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import           Text.Printf ( printf )++import qualified Data.Parameterized.NatRepr as PN+import qualified Data.Parameterized.Ctx as Ctx+import qualified Data.Parameterized.Context as Ctx+import           Data.Parameterized.Classes+import           Data.Parameterized.Some ( Some(..) )+import qualified Data.Parameterized.TraversableFC as FC+import           What4.BaseTypes++import qualified What4.Expr.ArrayUpdateMap as WAU+import qualified What4.Expr.Builder as W4+import qualified What4.IndexLit as WIL+import qualified What4.Interface as W4++import           What4.Serialize.SETokens ( Atom(..), printSExpr, parseSExpr )+import qualified What4.Utils.Serialize as U+import           What4.Serialize.Printer ( SExpr )++import           Prelude++data SomeSymFn t = forall dom ret. SomeSymFn (W4.SymFn t dom ret)++data Config sym =+  Config+  { cSymFnLookup :: Text -> IO (Maybe (SomeSymFn sym))+  -- ^ The mapping of names to defined What4 SymFns.+  , cExprLookup :: Text -> IO (Maybe (Some (W4.SymExpr sym)))+  -- ^ The mapping of names to defined What4 expressions.+  }++defaultConfig :: (W4.IsSymExprBuilder sym, ShowF (W4.SymExpr sym)) => sym -> Config sym+defaultConfig _sym = Config { cSymFnLookup = const (return Nothing)+                            , cExprLookup = const (return Nothing)+                            }+++-- | The lexical environment for parsing s-expressions and+-- procesing them into What4 terms.+data ProcessorEnv sym =+  ProcessorEnv+  { procSym :: sym+  -- ^ The symbolic What4 backend being used.+  , procSymFnLookup :: Text -> IO (Maybe (SomeSymFn sym))+    -- ^ The user-specified mapping of names to defined What4 SymFns.+  , procExprLookup :: Text -> IO (Maybe (Some (W4.SymExpr sym)))+  -- ^ The user-specified mapping of names to defined What4 expressions.+  , procLetEnv :: HM.HashMap Text (Some (W4.SymExpr sym))+  -- ^ The current lexical environment w.r.t. let-bindings+  -- encountered while parsing. N.B., these bindings are+  -- checked _before_ the \"global\" bindings implied by the+  -- user-specified lookup functions.+  , procLetFnEnv :: HM.HashMap Text (SomeSymFn sym)+  -- ^ The current lexical symfn environment+  -- w.r.t. letfn-bindings encountered while parsing. N.B.,+  -- these bindings are checked /before/ the \"global\"+  -- bindings implied by the user-specified lookup+  -- functions.+  }++type Processor sym a = E.ExceptT String (R.ReaderT (ProcessorEnv sym) IO) a++runProcessor :: ProcessorEnv sym -> Processor sym a -> IO (Either String a)+runProcessor env action = R.runReaderT (E.runExceptT action) env++lookupExpr :: Text -> Processor sym (Maybe (Some (W4.SymExpr sym)))+lookupExpr nm = do+  userLookupFn <- R.asks procExprLookup+  letEnv <- R.asks procLetEnv+  case HM.lookup nm letEnv of+    Nothing -> liftIO $ userLookupFn nm+    res -> return res++lookupFn :: Text -> Processor sym (Maybe (SomeSymFn sym))+lookupFn nm = do+  userLookupFn <- R.asks procSymFnLookup+  letEnv <- R.asks procLetFnEnv+  case HM.lookup nm letEnv of+    Nothing -> liftIO $ userLookupFn nm+    res -> return res+++-- | @(deserializeExpr sym)@ is equivalent+-- to @(deserializeExpr' (defaultConfig sym))@.+deserializeExpr ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => sym+  -> SExpr+  -> IO (Either String (Some (W4.SymExpr sym)))+deserializeExpr sym = deserializeExprWithConfig sym cfg+  where cfg = defaultConfig sym++deserializeExprWithConfig ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => sym+  -> Config sym+  -> SExpr+  -> IO (Either String (Some (W4.SymExpr sym)))+deserializeExprWithConfig sym cfg sexpr = runProcessor env (readExpr sexpr)+  where env = ProcessorEnv { procSym = sym+                           , procSymFnLookup = cSymFnLookup cfg+                           , procExprLookup = cExprLookup cfg+                           , procLetEnv = HM.empty+                           , procLetFnEnv = HM.empty+                           }++-- | @(deserializeSymFn sym)@ is equivalent+-- to @(deserializeSymFn' (defaultConfig sym))@.+deserializeSymFn ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => sym+  -> SExpr+  -> IO (Either String (SomeSymFn sym))+deserializeSymFn sym = deserializeSymFnWithConfig sym cfg+  where cfg = defaultConfig sym++deserializeSymFnWithConfig ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => sym+  -> Config sym+  -> SExpr+  -> IO (Either String (SomeSymFn sym))+deserializeSymFnWithConfig sym cfg sexpr = runProcessor env (readSymFn sexpr)+  where env = ProcessorEnv { procSym = sym+                           , procSymFnLookup = cSymFnLookup cfg+                           , procExprLookup = cExprLookup cfg+                           , procLetEnv = HM.empty+                           , procLetFnEnv = HM.empty+                           }+++deserializeBaseType ::+  SExpr+  -> Either String (Some BaseTypeRepr)+deserializeBaseType sexpr = readBaseType sexpr++++++-- * First pass of parsing turns the raw text into s-expressions.+--   This pass is handled by the code in What4.Serialize.SETokens++-- * Second pass of parsing: turning the s-expressions into symbolic expressions+-- and the overall templated formula++-- ** Utility functions++-- | Utility function for contextualizing errors. Prepends the given prefix+-- whenever an error is thrown.+prefixError :: (Monoid e, E.MonadError e m) => e -> m a -> m a+prefixError prefix act = E.catchError act (E.throwError . mappend prefix)++-- | Utility function for lifting a 'Maybe' into a 'MonadError'+fromMaybeError :: (E.MonadError e m) => e -> Maybe a -> m a+fromMaybeError err = maybe (E.throwError err) return+++readBaseType ::+  forall m . (E.MonadError String m)+  => SExpr+  -> m (Some BaseTypeRepr)+readBaseType sexpr =+  case sexpr of+    S.WFSAtom (AId atom) ->+      case (T.unpack atom) of+        "Bool" -> return $ Some BaseBoolRepr+        "Int" -> return $ Some BaseIntegerRepr+        "Real" -> return $ Some BaseRealRepr+        "String" -> return $ Some (BaseStringRepr UnicodeRepr)+        "Complex" -> return $ Some BaseComplexRepr+        _ -> panic+    S.WFSList [(S.WFSAtom (AId "BV")), (S.WFSAtom (AInt w))]+      | Just (Some wRepr) <- someNat w+      , Just LeqProof <- testLeq (knownNat @1) wRepr+        -> return $ Some (BaseBVRepr wRepr)+      | otherwise+        -> panic+    S.WFSList [(S.WFSAtom (AId "Float")), (S.WFSAtom (AInt e)), (S.WFSAtom (AInt s))]+      | Just (Some eRepr) <- someNat e+      , Just (Some sRepr) <- someNat s+      , Just LeqProof <- testLeq (knownNat @2) eRepr+      , Just LeqProof <- testLeq (knownNat @2) sRepr+        -> return (Some (BaseFloatRepr (FloatingPointPrecisionRepr eRepr sRepr)))+      | otherwise -> panic+    S.WFSList [(S.WFSAtom (AId "Struct")), args] -> do+      Some tps <- readBaseTypes args+      return $ Some (BaseStructRepr tps)+    S.WFSList [S.WFSAtom (AId "Array"), ixArgs, tpArg] -> do+      Some ixs <- readBaseTypes ixArgs+      Some tp <- readBaseType tpArg+      case Ctx.viewAssign ixs of+        Ctx.AssignEmpty -> E.throwError $ "array type has no indices: " ++ show sexpr+        Ctx.AssignExtend _ _ -> return $ Some (BaseArrayRepr ixs tp)+    _ -> panic+  where+    panic = E.throwError $ "unknown base type: " ++ show sexpr++readBaseTypes ::+  forall m . (E.MonadError String m)+  => SExpr+  -> m (Some (Ctx.Assignment BaseTypeRepr))+readBaseTypes sexpr@(S.WFSAtom _) = E.throwError $ "expected list of base types: " ++ show sexpr+readBaseTypes (S.WFSList sexprs) = Ctx.fromList <$> mapM readBaseType sexprs++-- ** Parsing definitions++-- | Stores a NatRepr along with proof that its type parameter is a bitvector of+-- that length. Used for easy pattern matching on the LHS of a binding in a+-- do-expression to extract the proof.+data BVProof tp where+  BVProof :: forall n. (1 <= n) => NatRepr n -> BVProof (BaseBVType n)++-- | Given an expression, monadically either returns proof that it is a+-- bitvector or throws an error.+getBVProof :: (W4.IsExpr ex, E.MonadError String m) => ex tp -> m (BVProof tp)+getBVProof expr =+  case W4.exprType expr of+    BaseBVRepr n -> return $ BVProof n+    t -> E.throwError $ printf "expected BV, found %s" (show t)+++-- | Operator type descriptions for parsing s-expression of+-- the form @(operator operands ...)@.+data Op sym where+    FloatOp1 :: (forall fpp . sym ->+                 W4.SymFloat sym fpp ->+                 IO (W4.SymFloat sym fpp))+             -> Op sym+    -- | Generic unary operator description.+    Op1 :: Ctx.Assignment BaseTypeRepr (Ctx.EmptyCtx Ctx.::> arg1)+        -> (sym ->+            W4.SymExpr sym arg1 ->+            IO (W4.SymExpr sym ret))+        -> Op sym+    -- | Generic dyadic operator description.+    Op2 :: Ctx.Assignment BaseTypeRepr (Ctx.EmptyCtx Ctx.::> arg1 Ctx.::> arg2)+        -> (sym ->+            W4.SymExpr sym arg1 ->+            W4.SymExpr sym arg2 ->+            IO (W4.SymExpr sym ret))+        -> Op sym+    -- | Generic triadic operator description.+    Op3 :: Ctx.Assignment BaseTypeRepr (Ctx.EmptyCtx Ctx.::> arg1 Ctx.::> arg2 Ctx.::> arg3)+        -> (sym ->+            W4.SymExpr sym arg1 ->+            W4.SymExpr sym arg2 ->+            W4.SymExpr sym arg3 ->+            IO (W4.SymExpr sym ret)+           )+        -> Op sym+    -- | Generic tetradic operator description.+    Op4 :: Ctx.Assignment+           BaseTypeRepr+           (Ctx.EmptyCtx Ctx.::> arg1 Ctx.::> arg2 Ctx.::> arg3 Ctx.::> arg4)+        -> ( sym ->+             W4.SymExpr sym arg1 ->+             W4.SymExpr sym arg2 ->+             W4.SymExpr sym arg3 ->+             W4.SymExpr sym arg4 ->+             IO (W4.SymExpr sym ret)+           )+        -> Op sym+    -- | Encapsulating type for a unary operation that takes one bitvector and+    -- returns another (in IO).+    BVOp1 :: (forall w . (1 <= w) =>+               sym ->+               W4.SymBV sym w ->+               IO (W4.SymBV sym w))+          -> Op sym+    -- | Binop with a bitvector return type, e.g., addition or bitwise operations.+    BVOp2 :: (forall w . (1 <= w) =>+               sym ->+               W4.SymBV sym w ->+               W4.SymBV sym w ->+               IO (W4.SymBV sym w))+          -> Op sym+    -- | Bitvector binop with a boolean return type, i.e., comparison operators.+    BVComp2 :: (forall w . (1 <= w) =>+                    sym ->+                    W4.SymBV sym w ->+                    W4.SymBV sym w ->+                    IO (W4.Pred sym))+                -> Op sym++-- | Lookup mapping operators to their Op definitions (if they exist)+lookupOp :: forall sym . W4.IsSymExprBuilder sym => Text -> Maybe (Op sym)+lookupOp name = HM.lookup name opTable+++opTable :: (W4.IsSymExprBuilder sym) => HM.HashMap Text (Op sym)+opTable =+  HM.fromList [+  -- -- -- Boolean ops -- -- --+    ("andp", Op2 knownRepr $ W4.andPred)+  , ("orp", Op2 knownRepr $ W4.orPred)+  , ("xorp", Op2 knownRepr $ W4.xorPred)+  , ("notp", Op1 knownRepr $ W4.notPred)+  -- -- -- Float ops -- -- --+  , ("floatneg", FloatOp1 W4.floatNeg)+  , ("floatabs", FloatOp1 W4.floatAbs)+  -- -- -- Integer ops -- -- --+  , ("intmul", Op2 knownRepr $ W4.intMul)+  , ("intadd", Op2 knownRepr $ W4.intAdd)+  , ("intmod", Op2 knownRepr $ W4.intMod)+  , ("intdiv", Op2 knownRepr $ W4.intDiv)+  , ("intle", Op2 knownRepr $ W4.intLe)+  , ("intabs", Op1 knownRepr $ W4.intAbs)+  -- -- -- Bitvector ops -- -- --+  , ("bvand", BVOp2 W4.bvAndBits)+  , ("bvor", BVOp2 W4.bvOrBits)+  , ("bvadd", BVOp2 W4.bvAdd)+  , ("bvmul", BVOp2 W4.bvMul)+  , ("bvudiv", BVOp2 W4.bvUdiv)+  , ("bvurem", BVOp2 W4.bvUrem)+  , ("bvshl", BVOp2 W4.bvShl)+  , ("bvlshr", BVOp2 W4.bvLshr)+  , ("bvnand", BVOp2 $ \sym arg1 arg2 -> W4.bvNotBits sym =<< W4.bvAndBits sym arg1 arg2)+  , ("bvnor", BVOp2 $ \sym arg1 arg2 -> W4.bvNotBits sym =<< W4.bvOrBits sym arg1 arg2)+  , ("bvxor", BVOp2 W4.bvXorBits)+  , ("bvxnor", BVOp2 $ \sym arg1 arg2 -> W4.bvNotBits sym =<< W4.bvXorBits sym arg1 arg2)+  , ("bvsub", BVOp2 W4.bvSub)+  , ("bvsdiv", BVOp2 W4.bvSdiv)+  , ("bvsrem", BVOp2 W4.bvSrem)+  , ("bvsmod", error "bvsmod is not implemented")+  , ("bvashr", BVOp2 W4.bvAshr)+  , ("bvult", BVComp2 W4.bvUlt)+  , ("bvule", BVComp2 W4.bvUle)+  , ("bvugt", BVComp2 W4.bvUgt)+  , ("bvuge", BVComp2 W4.bvUge)+  , ("bvslt", BVComp2 W4.bvSlt)+  , ("bvsle", BVComp2 W4.bvSle)+  , ("bvsgt", BVComp2 W4.bvSgt)+  , ("bvsge", BVComp2 W4.bvSge)+  , ("bveq", BVComp2 W4.bvEq)+  , ("bvne", BVComp2 W4.bvNe)+  , ("bvneg", BVOp1 W4.bvNeg)+  , ("bvnot", BVOp1 W4.bvNotBits)+  -- -- -- Floating point ops -- -- --+  , ("fnegd", Op1 knownRepr $ W4.floatNeg @_ @Prec64)+  , ("fnegs", Op1 knownRepr $ W4.floatNeg @_ @Prec32)+  , ("fabsd", Op1 knownRepr $ W4.floatAbs @_ @Prec64)+  , ("fabss", Op1 knownRepr $ W4.floatAbs @_ @Prec32)+  , ("fsqrt", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatSqrt @_ @Prec64 sym rm x)+  , ("fsqrts", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatSqrt @_ @Prec32 sym rm x)+  , ("fnand", Op1 knownRepr $ W4.floatIsNaN @_ @Prec64)+  , ("fnans", Op1 knownRepr $ W4.floatIsNaN @_ @Prec32)+  , ("frsp", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatCast @_ @Prec32 @Prec64 sym knownRepr rm x)+  , ("fp_single_to_double", Op1 knownRepr $ \sym ->+    W4.floatCast @_ @Prec64 @Prec32 sym knownRepr W4.RNE)+  , ("fp_binary_to_double",+     Op1 knownRepr $ \sym -> W4.floatFromBinary @_ @11 @53 sym knownRepr)+  , ("fp_binary_to_single",+     Op1 knownRepr $ \sym -> W4.floatFromBinary @_ @8 @24 sym knownRepr)+  , ("fp_double_to_binary", Op1 knownRepr $ W4.floatToBinary @_ @11 @53)+  , ("fp_single_to_binary", Op1 knownRepr $ W4.floatToBinary @_ @8 @24)+  , ("fctid", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatToSBV @_ @64 @Prec64 sym knownRepr rm x)+  , ("fctidu", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatToBV @_ @64 @Prec64 sym knownRepr rm x)+  , ("fctiw", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatToSBV @_ @32 @Prec64 sym knownRepr rm x)+  , ("fctiwu", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatToBV @_ @32 @Prec64 sym knownRepr rm x)+  , ("fcfid", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.sbvToFloat @_ @64 @Prec64 sym knownRepr rm x)+  , ("fcfids", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.sbvToFloat @_ @64 @Prec32 sym knownRepr rm x)+  , ("fcfidu", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.bvToFloat @_ @64 @Prec64 sym knownRepr rm x)+  , ("fcfidus", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.bvToFloat @_ @64 @Prec32 sym knownRepr rm x)+  , ("frti", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatRound @_ @Prec64 sym rm x)+  , ("frtis", Op2 knownRepr $ \sym r x -> U.withRounding sym r $ \rm ->+    W4.floatRound @_ @Prec32 sym rm x)+  , ("fadd", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatAdd @_ @Prec64 sym rm x y)+  , ("fadds", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatAdd @_ @Prec32 sym rm x y)+  , ("fsub", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatSub @_ @Prec64 sym rm x y)+  , ("fsubs", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatSub @_ @Prec32 sym rm x y)+  , ("fmul", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatMul @_ @Prec64 sym rm x y)+  , ("fmuls", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatMul @_ @Prec32 sym rm x y)+  , ("fdiv", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatDiv @_ @Prec64 sym rm x y)+  , ("fdivs", Op3 knownRepr $ \sym r x y -> U.withRounding sym r $ \rm ->+    W4.floatDiv @_ @Prec32 sym rm x y)+  , ("fltd", Op2 knownRepr $ W4.floatLt @_ @Prec64)+  , ("flts", Op2 knownRepr $ W4.floatLt @_ @Prec32)+  , ("feqd", Op2 knownRepr $ W4.floatFpEq @_ @Prec64)+  , ("feqs", Op2 knownRepr $ W4.floatFpEq @_ @Prec32)+  , ("fled", Op2 knownRepr $ W4.floatLe @_ @Prec64)+  , ("fles", Op2 knownRepr $ W4.floatLe @_ @Prec32)+  , ("ffma", Op4 knownRepr $ \sym r x y z -> U.withRounding sym r $ \rm ->+    W4.floatFMA @_ @Prec64 sym rm x y z)+  , ("ffmas", Op4 knownRepr $ \sym r x y z ->+    U.withRounding sym r $ \rm -> W4.floatFMA @_ @Prec32 sym rm x y z)+  ]++-- | Verify a list of arguments has a single argument and+-- return it, else raise an error.+readOneArg ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -> Processor sym (Some (W4.SymExpr sym))+readOneArg operands = do+  args <- readExprs operands+  case args of+    [arg] -> return arg+    _ -> E.throwError $ printf "expecting 1 argument, got %d" (length args)++-- | Verify a list of arguments has two arguments and return+-- it, else raise an error.+readTwoArgs ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -> Processor sym (Some (W4.SymExpr sym), Some (W4.SymExpr sym))+readTwoArgs operands = do+  args <- readExprs operands+  case args of+    [arg1, arg2] -> return (arg1, arg2)+    _ -> E.throwError $ printf "expecting 2 arguments, got %d" (length args)++-- | Verify a list of arguments has three arguments and+-- return it, else raise an error.+readThreeArgs ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -> Processor sym (Some (W4.SymExpr sym), Some (W4.SymExpr sym), Some (W4.SymExpr sym))+readThreeArgs operands = do+  args <- readExprs operands+  case args of+    [arg1, arg2, arg3] -> return (arg1, arg2, arg3)+    _ -> E.throwError $ printf "expecting 3 arguments, got %d" (length args)++++-- | Reads an "application" form, i.e. @(operator operands ...)@.+readApp ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => SExpr+  -> [SExpr]+  -> Processor sym (Some (W4.SymExpr sym))+readApp (S.WFSAtom (AId "call")) (S.WFSAtom (AId fnName):operands) = do+  sym <- R.asks procSym+  maybeFn <- lookupFn fnName+  case maybeFn of+    Just (SomeSymFn fn) -> do+      args <- mapM readExpr operands+      assn <- exprAssignment (W4.fnArgTypes fn) args+      liftIO (Some <$> W4.applySymFn sym fn assn)+    Nothing -> E.throwError $ "The function name `"+               ++(T.unpack fnName)+               ++"` is not bound to a SymFn in the current Config."+readApp opRaw@(S.WFSAtom (AId "call")) operands = E.throwError+  $ "Unrecognized use of `call`: " ++ (T.unpack (printSExpr mempty (S.L (opRaw:operands))))+readApp opRaw@(S.WFSAtom (AId operator)) operands = do+  sym <- R.reader procSym+  prefixError ("in reading expression:\n"+               ++(T.unpack $ printSExpr mempty $ S.WFSList (opRaw:operands))++"\n") $+  -- Parse an expression of the form @(fnname operands ...)@+    case lookupOp @sym operator of+      Just (FloatOp1 fn) -> do+        args <- readExprs operands+        case args of+          [Some a1]+            | BaseFloatRepr _ <- W4.exprType a1 -> liftIO (Some <$> fn sym a1)+          _ -> E.throwError "Unable to unpack FloatOp1 arg in Formula.Parser readApp"+      Just (Op1 arg_types fn) -> do+        args <- readExprs operands+        exprAssignment arg_types args >>= \case+          Ctx.Empty Ctx.:> arg1 ->+            liftIO (Some <$> fn sym arg1)+      Just (Op2 arg_types fn) -> do+        args <- readExprs operands+        exprAssignment arg_types args >>= \case+          Ctx.Empty Ctx.:> arg1 Ctx.:> arg2 ->+              liftIO (Some <$> fn sym arg1 arg2)+      Just (Op3 arg_types fn) -> do+        args <- readExprs operands+        exprAssignment arg_types args >>= \case+          Ctx.Empty Ctx.:> arg1 Ctx.:> arg2 Ctx.:> arg3 ->+              liftIO (Some <$> fn sym arg1 arg2 arg3)+      Just (Op4 arg_types fn) -> do+        args <- readExprs operands+        exprAssignment arg_types args >>= \case+          Ctx.Empty Ctx.:> arg1 Ctx.:> arg2 Ctx.:> arg3 Ctx.:> arg4 ->+              liftIO (Some <$> fn sym arg1 arg2 arg3 arg4)+      Just (BVOp1 op) -> do+        Some expr <- readOneArg operands+        BVProof _ <- getBVProof expr+        liftIO $ Some <$> op sym expr+      Just (BVOp2 op) -> do+        (Some arg1, Some arg2)  <- readTwoArgs operands+        BVProof m <- prefixError "in arg 1: " $ getBVProof arg1+        BVProof n <- prefixError "in arg 2: " $ getBVProof arg2+        case testEquality m n of+          Just Refl -> liftIO (Some <$> op sym arg1 arg2)+          Nothing -> E.throwError $ printf "arguments to %s must be the same length, \+                                         \but arg 1 has length %s \+                                         \and arg 2 has length %s"+                                         operator+                                         (show m)+                                         (show n)+      Just (BVComp2 op) -> do+        (Some arg1, Some arg2)  <- readTwoArgs operands+        BVProof m <- prefixError "in arg 1: " $ getBVProof arg1+        BVProof n <- prefixError "in arg 2: " $ getBVProof arg2+        case testEquality m n of+          Just Refl -> liftIO (Some <$> op sym arg1 arg2)+          Nothing -> E.throwError $ printf "arguments to %s must be the same length, \+                                         \but arg 1 has length %s \+                                         \and arg 2 has length %s"+                                         operator+                                         (show m)+                                         (show n)+      Nothing ->+        -- Operators/syntactic-forms with types too+        -- complicated to nicely fit in the Op type+        case operator of+          "concat" -> do+            (Some arg1, Some arg2)  <- readTwoArgs operands+            BVProof _ <- prefixError "in arg 1: " $ getBVProof arg1+            BVProof _ <- prefixError "in arg 2: " $ getBVProof arg2+            liftIO (Some <$> W4.bvConcat sym arg1 arg2)+          "=" -> do+            (Some arg1, Some arg2)  <- readTwoArgs operands+            case testEquality (W4.exprType arg1) (W4.exprType arg2) of+              Just Refl -> liftIO (Some <$> W4.isEq sym arg1 arg2)+              Nothing -> E.throwError $+                printf "arguments must have same types, \+                       \but arg 1 has type %s \+                       \and arg 2 has type %s"+                (show (W4.exprType arg1))+                (show (W4.exprType arg2))+          "ite" -> do+            (Some test, Some then_, Some else_)  <- readThreeArgs operands+            case W4.exprType test of+              BaseBoolRepr ->+                case testEquality (W4.exprType then_) (W4.exprType else_) of+                  Just Refl -> liftIO (Some <$> W4.baseTypeIte sym test then_ else_)+                  Nothing -> E.throwError $+                    printf "then and else branches must have same type, \+                           \but then has type %s \+                           \and else has type %s"+                    (show (W4.exprType then_))+                    (show (W4.exprType else_))+              tp -> E.throwError $ printf "test expression must be a boolean; got %s" (show tp)+          "select" -> do+            (Some arr, Some idx)  <- readTwoArgs operands+            ArraySingleDim _ <- expectArrayWithIndex (W4.exprType idx) (W4.exprType arr)+            let idx' = Ctx.empty Ctx.:> idx+            liftIO (Some <$> W4.arrayLookup sym arr idx')+          "store" -> do+            (Some arr, Some idx, Some expr)  <- readThreeArgs operands+            ArraySingleDim resRepr <- expectArrayWithIndex (W4.exprType idx) (W4.exprType arr)+            case testEquality resRepr (W4.exprType expr) of+              Just Refl ->+                let idx' = Ctx.empty Ctx.:> idx+                in liftIO (Some <$> W4.arrayUpdate sym arr idx' expr)+              Nothing -> E.throwError $ printf "Array result type %s does not match %s"+                         (show resRepr)+                         (show (W4.exprType expr))+          "updateArray" -> do+            (Some arr, Some idx, Some expr)  <- readThreeArgs operands+            ArraySingleDim resRepr <- expectArrayWithIndex (W4.exprType idx) (W4.exprType arr)+            case testEquality resRepr (W4.exprType expr) of+              Just Refl ->+                let idx' = Ctx.empty Ctx.:> idx+                in liftIO (Some <$> W4.arrayUpdate sym arr idx' expr)+              Nothing -> E.throwError $ printf "Array result type %s does not match %s"+                         (show resRepr)+                         (show (W4.exprType expr))++          "arrayMap" ->+            -- arrayMap(idxs, array)++            -- The list of indexes is a list of pairs where each pair is:+            --+            -- > (indexList, expr)+++            -- Each list of indexes is a list of 'IndexLit' (since we have multi-dimensional indexing)+            case operands of+              [updateSExprList, arrSExpr] -> do+                Some arrExpr <- readExpr arrSExpr+                case W4.exprType arrExpr of+                  BaseArrayRepr idxReprs arrTyRepr -> do+                    updateMap <- expectArrayUpdateMap idxReprs arrTyRepr updateSExprList+                    liftIO (Some <$> W4.sbMakeExpr sym (W4.ArrayMap idxReprs arrTyRepr updateMap arrExpr))+                  repr -> E.throwError $ unwords ["expected an array type for the value in 'arrayMap', but got", show repr]+              _ -> E.throwError $ unwords ["expected a list of indices and an array expression, but got", show operands]++          "field" -> do+            case operands of+              [rawStruct, S.WFSAtom (AInt rawIdx)] -> do+                Some struct  <- readExpr rawStruct+                case W4.exprType struct of+                  (BaseStructRepr fldTpReprs) ->+                    case Ctx.intIndex (fromInteger rawIdx) (Ctx.size fldTpReprs) of+                      Just (Some i) -> liftIO (Some <$> W4.structField sym struct i)+                      Nothing -> E.throwError $+                        unwords ["invalid struct index, got", show fldTpReprs, "and", show rawIdx]+                  srepr -> E.throwError $ unwords ["expected a struct, got", show srepr]+              _ -> E.throwError $ unwords ["expected an arg and an Int, got", show operands]+          "struct" -> do+            case operands of+              [S.WFSList rawFldExprs] -> do+                Some flds <- readExprsAsAssignment rawFldExprs+                liftIO (Some <$> W4.mkStruct sym flds)+              _ -> E.throwError $ unwords ["struct expects a single operand, got", show operands]+          "sbvToInteger" -> do+            (Some arg) <- readOneArg operands+            BVProof _ <- getBVProof arg+            liftIO $ Some <$> W4.sbvToInteger sym arg+          "bvToInteger" -> do+            (Some arg) <- readOneArg operands+            BVProof _ <- getBVProof arg+            liftIO $ Some <$> W4.bvToInteger sym arg+          "integerToBV" -> do+            case operands of+              [S.WFSAtom (ANat width), rawValExpr] -> do+                Some x <- readExpr rawValExpr+                case (mkNatRepr width, W4.exprType x) of+                  (Some w, BaseIntegerRepr)+                    | Just LeqProof <- isPosNat w -> do+                    liftIO (Some <$> W4.integerToBV sym x w)+                  srepr -> E.throwError $ unwords ["expected a non-zero natural and an integer, got", show srepr]+              _ -> E.throwError $ unwords ["integerToBV expects two operands, the first of which is a nat, got", show operands]+          _ -> E.throwError $ printf "couldn't parse application of %s" (printSExpr mempty opRaw)+-- Parse an expression of the form @((_ extract i j) x)@.+readApp (S.WFSList [S.WFSAtom (AId "_"), S.WFSAtom (AId "extract"), S.WFSAtom (AInt iInt), S.WFSAtom (AInt jInt)])+  args = prefixError "in reading extract expression: " $ do+  sym <- R.reader procSym+  (Some arg) <- readOneArg args+  -- The SMT-LIB spec represents extracts differently than Crucible does. Per+  -- SMT: "extraction of bits i down to j from a bitvector of size m to yield a+  -- new bitvector of size n, where n = i - j + 1". Per Crucible:+  --+  -- > -- | Select a subsequence from a bitvector.+  -- > bvSelect :: (1 <= n, idx + n <= w)+  -- >          => sym+  -- >          -> NatRepr idx  -- ^ Starting index, from 0 as least significant bit+  -- >          -> NatRepr n    -- ^ Number of bits to take+  -- >          -> SymBV sym w  -- ^ Bitvector to select from+  -- >          -> IO (SymBV sym n)+  --+  -- The "starting index" seems to be from the bottom, so that (in slightly+  -- pseudocode)+  --+  -- > > bvSelect sym 0 8 (0x01020304:[32])+  -- > 0x4:[8]+  -- > > bvSelect sym 24 8 (0x01020304:[32])+  -- > 0x1:[8]+  --+  -- Thus, n = i - j + 1, and idx = j.+  let nInt = iInt - jInt + 1+      idxInt = jInt+  Some nNat <- prefixError "in calculating extract length: " $ intToNatM nInt+  Some idxNat <- prefixError "in extract lower bound: " $ intToNatM idxInt+  LeqProof <- fromMaybeError "extract length must be positive" $ isPosNat nNat+  BVProof lenNat <- getBVProof arg+  LeqProof <- fromMaybeError "invalid extract for given bitvector" $+    testLeq (addNat idxNat nNat) lenNat+  liftIO (Some <$> W4.bvSelect sym idxNat nNat arg)+-- Parse an expression of the form @((_ zero_extend i) x)@ or @((_ sign_extend i) x)@.+readApp (S.WFSList [S.WFSAtom (AId "_"), S.WFSAtom (AId extend), S.WFSAtom (AInt iInt)])+  args+  | extend == "zero_extend" ||+    extend == "sign_extend" = prefixError (printf "in reading %s expression: " extend) $ do+      sym <- R.reader procSym+      Some arg <- readOneArg args+      Some iNat <- intToNatM iInt+      iPositive <- fromMaybeError "must extend by a positive length" $ isPosNat iNat+      BVProof lenNat <- getBVProof arg+      let newLen = addNat lenNat iNat+      liftIO $ withLeqProof (leqAdd2 (leqRefl lenNat) iPositive) $+        let op = if extend == "zero_extend" then W4.bvZext else W4.bvSext+        in Some <$> op sym newLen arg+readApp (S.WFSList [S.WFSAtom (AId "_"), S.WFSAtom (AId "bvfill"), S.WFSAtom (AInt width)]) args =+  prefixError "in reading bvfill expression" $ do+    sym <- R.reader procSym+    Some arg <- readOneArg args+    case W4.exprType arg of+      BaseBoolRepr -> do+        Some widthRep <- intToNatM width+        LeqProof <- fromMaybeError "must extend by a positive length" $ isPosNat widthRep+        liftIO (Some <$> W4.bvFill sym widthRep arg)+      tyrep -> E.throwError ("Invalid argument type to bvFill: " ++ show tyrep)+readApp rator rands = E.throwError $ ("readApp could not parse the following: "+                                      ++ (T.unpack (printSExpr mempty $ S.WFSList (rator:rands))))+++-- | Try converting an 'Integer' to a 'NatRepr' or throw an error if not+-- possible.+intToNatM :: (E.MonadError String m) => Integer -> m (Some NatRepr)+intToNatM = fromMaybeError "integer must be non-negative to be a nat" . someNat++-- | Parse a list of array updates where each entry in the list is:+--+-- > (idxs, elt)+--+-- where each @idxs@ is a list (assignment) of indexes (with type @idxReprs@)+-- and each element is an expr.+--+-- NOTE: We assume that there are no duplicates in the list and apply the+-- updates in an arbitrary order.  This is true for any map serialized by this+-- library.+expectArrayUpdateMap+  :: forall sym t st fs tp i itp+   . (sym ~ W4.ExprBuilder t st fs)+  => Ctx.Assignment BaseTypeRepr (i Ctx.::> itp)+  -> BaseTypeRepr tp+  -> SExpr+  -> Processor sym (WAU.ArrayUpdateMap (W4.SymExpr sym) (i Ctx.::> itp) tp)+expectArrayUpdateMap idxReprs arrTyRepr updateSExprList =+  case updateSExprList of+    S.L items -> F.foldrM expectArrayUpdateEntry WAU.empty items+    _ -> E.throwError "Expected a list of array element updates in ArrayMap"+  where+    expectArrayUpdateEntry pair updateMap =+      case pair of+        S.L [S.L idxListExprs, elt] -> do+          idxs <- Ctx.traverseWithIndex (parseIndexLit idxListExprs) idxReprs+          Some x <- readExpr elt+          case testEquality arrTyRepr (W4.exprType x) of+            Just Refl -> return (WAU.insert arrTyRepr idxs x updateMap)+            Nothing -> E.throwError (concat [ "Invalid element type in ArrayMap update: expected "+                                            , show arrTyRepr+                                            , " but got "+                                            , show (W4.exprType x)])+        _ -> E.throwError "Unexpected ArrayMap update item structure"++-- | Safe list indexing+--+-- This version only traverses the list once (compared to computing the length+-- and then using unsafe indexing)+(!?) :: [a] -> Int -> Maybe a+lst !? idx+  | idx < 0 = Nothing+  | otherwise = go idx lst+  where+    go 0 (x:_xs) = Just x+    go i (_:xs) = go (i - 1) xs+    go _ [] = Nothing++-- | Parse a single 'WIL.IndexLit' out of a list of 'SExpr' (at the named index)+--+-- This is used to build the assignment of indexes+parseIndexLit :: [SExpr]+               -> Ctx.Index ctx tp+               -> BaseTypeRepr tp+               -> Processor sym (WIL.IndexLit tp)+parseIndexLit exprs idx repr+  | Just (S.A atom) <- exprs !? Ctx.indexVal idx =+      case (repr, atom) of+        (BaseBVRepr w, ABV w' val)+          | PN.intValue w == toInteger w' ->+            return (WIL.BVIndexLit w (BV.mkBV w val))+          | otherwise -> E.throwError ("Array update index bitvector size mismatch: expected " ++ show w ++ " but got " ++ show w')+        (BaseIntegerRepr, AInt i) -> return (WIL.IntIndexLit i)+        _ -> E.throwError ("Unexpected array update index type: " ++ show repr)+  | otherwise = E.throwError ("Invalid or missing array update index at " ++ show idx)++data ArrayJudgment :: BaseType -> BaseType -> Type where+  ArraySingleDim :: forall idx res.+                    BaseTypeRepr res+                 -> ArrayJudgment idx (BaseArrayType (Ctx.SingleCtx idx) res)++expectArrayWithIndex :: (E.MonadError String m) => BaseTypeRepr tp1 -> BaseTypeRepr tp2 -> m (ArrayJudgment tp1 tp2)+expectArrayWithIndex dimRepr (BaseArrayRepr idxTpReprs resRepr) =+  case Ctx.viewAssign idxTpReprs of+    Ctx.AssignExtend rest idxTpRepr ->+      case Ctx.viewAssign rest of+        Ctx.AssignEmpty ->+          case testEquality idxTpRepr dimRepr of+            Just Refl -> return $ ArraySingleDim resRepr+            Nothing -> E.throwError $ unwords ["Array index type", show idxTpRepr,+                                               "does not match", show dimRepr]+        _ -> E.throwError "multidimensional arrays are not supported"+expectArrayWithIndex _ repr = E.throwError $ unwords ["expected an array, got", show repr]++exprAssignment ::+  forall sym ctx ex . (W4.IsSymExprBuilder sym, ShowF (W4.SymExpr sym), ShowF ex, W4.IsExpr ex)+  => Ctx.Assignment BaseTypeRepr ctx+  -> [Some ex]+  -> Processor sym (Ctx.Assignment ex ctx)+exprAssignment tpAssns exs = do+  Some exsAsn <- return $ Ctx.fromList exs+  exsRepr <- return $ FC.fmapFC W4.exprType exsAsn+  case testEquality exsRepr tpAssns of+    Just Refl -> return exsAsn+    Nothing -> E.throwError $+      "Unexpected expression types for " ++ show exsAsn+      ++ "\nExpected: " ++ show tpAssns+      ++ "\nGot: " ++ show exsRepr+++-- | Given the s-expressions for the bindings and body of a+-- let, parse the bindings into the Reader monad's state and+-- then parse the body with those newly bound variables.+readLetExpr ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -- ^ Bindings in a let-expression.+  -> SExpr+  -- ^ Body of the let-expression.+  -> Processor sym (Some (W4.SymExpr sym))+readLetExpr [] body = readExpr body+readLetExpr ((S.WFSList [S.WFSAtom (AId x), e]):rst) body = do+  v <- readExpr e+  R.local (\c -> c {procLetEnv = (HM.insert x v) $ procLetEnv c}) $+    readLetExpr rst body+readLetExpr bindings _body = E.throwError $+  "invalid s-expression for let-bindings: " ++ (show bindings)+++readLetFnExpr ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -- ^ Bindings in a let-expression.+  -> SExpr+  -- ^ Body of the let-expression.+  -> Processor sym (Some (W4.SymExpr sym))+readLetFnExpr [] body = readExpr body+readLetFnExpr ((S.WFSList [S.WFSAtom (AId f), e]):rst) body = do+  v <- readSymFn e+  R.local (\c -> c {procLetFnEnv = (HM.insert f v) $ procLetFnEnv c}) $+    readLetExpr rst body+readLetFnExpr bindings _body = E.throwError $+  "invalid s-expression for let-bindings: " ++ (show bindings)++  +-- | Parse an arbitrary expression.+readExpr ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => SExpr+  -> Processor sym (Some (W4.SymExpr sym))+readExpr (S.WFSAtom (AInt n)) = do+  sym <- R.reader procSym+  liftIO $ (Some <$> W4.intLit sym n)+readExpr (S.WFSAtom (ANat _)) =+  E.throwError "Bare Natural literals are no longer used"+readExpr (S.WFSAtom (ABool b)) = do+  sym <- R.reader procSym+  liftIO $ return $ Some $ W4.backendPred sym b+readExpr (S.WFSAtom (AFloat (Some repr) bf)) = do+  sym <- R.reader procSym+  liftIO $ (Some <$> W4.floatLit sym repr bf)+readExpr (S.WFSAtom (AStr prefix content)) = do+  sym <- R.reader procSym+  case prefix of+    (Some W4.UnicodeRepr) -> do+      s <- liftIO $ W4.stringLit sym $ W4.UnicodeLiteral content+      return $ Some $ s+    (Some W4.Char8Repr) -> do+      s <- liftIO $ W4.stringLit sym $ W4.Char8Literal $ T.encodeUtf8 content+      return $ Some $ s+    (Some W4.Char16Repr) -> E.throwError $ "Char16 strings are not yet supported"+readExpr (S.WFSAtom (AReal _)) = E.throwError $ "TODO: support readExpr for real literals"+readExpr (S.WFSAtom (ABV len val)) = do+  -- This is a bitvector literal.+  sym <- R.reader procSym+  -- The following two patterns should never fail, given that during parsing we+  -- can only construct BVs with positive length.+  case someNat (toInteger len) of+    Just (Some lenRepr) -> do+        pf <- case isPosNat lenRepr of+                Just pf -> return pf+                Nothing -> E.throwError "What4.Serialize.Parser.readExpr isPosNat failure"+        liftIO $ withLeqProof pf (Some <$> W4.bvLit sym lenRepr (BV.mkBV lenRepr val))+    Nothing -> E.throwError "SemMC.Formula.Parser.readExpr someNat failure"+  -- Just (Some lenRepr) <- return $ someNat (toInteger len)+  -- let Just pf = isPosNat lenRepr+  -- liftIO $ withLeqProof pf (Some <$> W4.bvLit sym lenRepr val)+-- Let-bound variable+readExpr (S.WFSAtom (AId name)) = do+  maybeBinding <- lookupExpr name+  -- We first check the local lexical environment (i.e., the+  -- in-scope let-bindings) before consulting the "global"+  -- scope.+  case maybeBinding of+    -- simply return it's bound value+    Just binding -> return binding+    Nothing -> E.throwError $ ("Unbound variable encountered during deserialization: "+                               ++ (T.unpack name))+readExpr (S.WFSList ((S.WFSAtom (AId "let")):rhs)) =+  case rhs of+    [S.WFSList bindings, body] -> readLetExpr bindings body+    _ -> E.throwError "ill-formed let s-expression"+readExpr (S.WFSList ((S.WFSAtom (AId "letfn")):rhs)) =+  case rhs of+    [S.WFSList bindings, body] -> readLetFnExpr bindings body+    _ -> E.throwError "ill-formed letfn s-expression"+readExpr (S.WFSList []) = E.throwError "ill-formed empty s-expression"+readExpr (S.WFSList (operator:operands)) = readApp operator operands++++-- | Parse multiple expressions in a list.+readExprs ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -> Processor sym [Some (W4.SymExpr sym)]+readExprs exprs = mapM readExpr exprs++readExprsAsAssignment ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => [SExpr]+  -> Processor sym (Some (Ctx.Assignment (W4.SymExpr sym)))+readExprsAsAssignment exprs = Ctx.fromList <$> readExprs exprs+++readFnType ::+  forall sym . (W4.IsSymExprBuilder sym, ShowF (W4.SymExpr sym))+  => SExpr+  -> Processor sym ([Some BaseTypeRepr], Some BaseTypeRepr)+readFnType (S.WFSList ((S.WFSAtom (AId "->")):typeSExprs)) =+  case unsnoc typeSExprs of+    Nothing ->+      E.throwError $ ("invalid type signature for function: "+                      ++ (T.unpack $ printSExpr mempty (S.L typeSExprs)))+    Just (domSExps, retSExp) -> do+      dom <- mapM readBaseType domSExps+      ret <- readBaseType retSExp+      return (dom, ret)+readFnType sexpr =+  E.throwError $ ("invalid type signature for function: "+                  ++ (T.unpack $ printSExpr mempty sexpr))++-- | If the list is empty, return 'Nothing'. If the list is non-empty, return+-- @'Just' (xs, x)@, where @xs@ is equivalent to calling 'init' on the list and+-- @x@ is equivalent to calling 'last' on the list.+unsnoc :: [a] -> Maybe ([a], a)+unsnoc []     = Nothing+unsnoc (x:xs) = case unsnoc xs of+                  Nothing    -> Just ([], x)+                  Just (a,b) -> Just (x:a, b)++readFnArgs ::+  forall sym . (W4.IsSymExprBuilder sym, ShowF (W4.SymExpr sym))+  => [SExpr]+  -> Processor sym [Text]+readFnArgs [] = return []+readFnArgs ((S.WFSAtom (AId name)):rest) = do+  names <- (readFnArgs rest)+  return $ name:names+readFnArgs (badArg:_) =+  E.throwError $ ("invalid function argument encountered: "+                  ++ (T.unpack $ printSExpr mempty badArg))++someVarExpr ::+    forall sym . (W4.IsSymExprBuilder sym, ShowF (W4.SymExpr sym))+  => sym+  -> Some (W4.BoundVar sym)+  -> Some (W4.SymExpr sym)+someVarExpr sym (Some bv) = Some (W4.varExpr sym bv)+++readSymFn ::+  forall sym t st fs . (sym ~ W4.ExprBuilder t st fs)+  => SExpr+  -> Processor sym (SomeSymFn sym)+readSymFn (S.WFSList [ S.WFSAtom (AId "definedfn")+                     , S.WFSAtom (AStr _ rawSymFnName)+                     , rawFnType+                     , S.WFSList argVarsRaw+                     , bodyRaw+                     ]) = do+  sym <- R.reader procSym+  symFnName <- case W4.userSymbol (T.unpack rawSymFnName) of+                 Left _ -> E.throwError $ ("Bad symbolic function name : "+                                           ++ (T.unpack rawSymFnName))+                 Right solverSym -> return solverSym+  argNames <- readFnArgs argVarsRaw+  (argTys, _retTy) <- readFnType rawFnType+  E.when (not (length argTys == length argNames)) $+    E.throwError $ "Function type expected "+    ++ (show $ length argTys)+    ++ " args but found "+    ++ (show $ length argNames)+  argVars <- mapM (\(name, (Some ty)) ->+                     case W4.userSymbol (T.unpack name) of+                       Left _ -> E.throwError $ "Bad arg name : " ++ (T.unpack name)+                       Right solverSym -> liftIO $ Some <$> W4.freshBoundVar sym solverSym ty)+             $ zip argNames argTys+  (Some body) <- let newBindings = HM.fromList+                                   $ zip argNames+                                   $ map (someVarExpr sym) argVars+                 in R.local+                    (\env -> env {procLetEnv = HM.union (procLetEnv env) newBindings})+                    $ readExpr bodyRaw+  Some argVarAssignment <- return $ Ctx.fromList argVars+  symFn <- liftIO $ W4.definedFn sym symFnName argVarAssignment body W4.UnfoldConcrete+  return $ SomeSymFn symFn+readSymFn badSExp@(S.WFSList ((S.WFSAtom (AId "definedfn")):_)) =+  E.throwError $ ("invalid `definedfn`: " ++ (T.unpack $ printSExpr mempty badSExp))+readSymFn (S.WFSList [ S.WFSAtom (AId "uninterpfn")+                     , S.WFSAtom (AStr _ rawSymFnName)+                     , rawFnType+                     ]) = do+  sym <- R.reader procSym+  symFnName <- case W4.userSymbol (T.unpack rawSymFnName) of+                 Left _ -> E.throwError $ ("Bad symbolic function name : "+                                           ++ (T.unpack rawSymFnName))+                 Right solverSym -> return solverSym+  (argTys, (Some retTy)) <- readFnType rawFnType+  Some domain <- return $ Ctx.fromList argTys+  symFn <- liftIO $ W4.freshTotalUninterpFn sym symFnName domain retTy+  return $ SomeSymFn symFn+readSymFn badSExp@(S.WFSList ((S.WFSAtom (AId "uninterpfn")):_)) =+  E.throwError $ ("invalid `uninterpfn`: " ++ (T.unpack $ printSExpr mempty badSExp))+readSymFn sexpr = E.throwError ("invalid function definition: "+                                ++ (T.unpack $ printSExpr mempty sexpr))
+ src/What4/Serialize/Printer.hs view
@@ -0,0 +1,779 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE PatternSynonyms #-}++module What4.Serialize.Printer+  (+    serializeExpr+  , serializeExprWithConfig+  , serializeSymFn+  , serializeSymFnWithConfig+  , serializeBaseType+  , convertBaseTypes+  , Config(..)+  , Result(..)+  , printSExpr+  , defaultConfig+  , SExpr+  , Atom(..)+  , SomeExprSymFn(..)+  , S.WellFormedSExpr(..)+  , ident, int, string+  , bitvec, bool, nat, real+  , ppFreeVarEnv+  , ppFreeSymFnEnv+  , pattern S.L+  , pattern S.A+  ) where++import           Numeric.Natural+import qualified Data.Foldable as F+import           Data.Set ( Set )+import qualified Data.Set as Set+import           Data.Map.Ordered (OMap)+import qualified Data.Map.Ordered as OMap+import qualified Data.BitVector.Sized as BV+import           Data.Parameterized.Some+import qualified Data.Parameterized.Context as Ctx+import qualified Data.Parameterized.NatRepr as NR+import qualified Data.Parameterized.Nonce as Nonce+import qualified Data.Parameterized.TraversableFC as FC++import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import           Data.Word ( Word64 )+import qualified Control.Monad as M+import           Control.Monad.State.Strict (State)+import qualified Control.Monad.State.Strict as MS++import qualified Data.SCargot.Repr.WellFormed as S++import           What4.BaseTypes+import qualified What4.Expr as W4+import qualified What4.Expr.ArrayUpdateMap as WAU+import qualified What4.Expr.BoolMap as BooM+import qualified What4.Expr.Builder as W4+import qualified What4.Expr.WeightedSum as WSum+import qualified What4.IndexLit as WIL+import qualified What4.Interface as W4+import qualified What4.Symbol as W4+import qualified What4.Utils.StringLiteral as W4S+++import           What4.Serialize.SETokens ( Atom(..), printSExpr+                                          , ident, int, nat, string+                                          , bitvec, bool, real, float+                                          )++type SExpr = S.WellFormedSExpr Atom++data SomeExprSymFn t = forall dom ret. SomeExprSymFn (W4.ExprSymFn t dom ret)++instance Eq (SomeExprSymFn t) where+  (SomeExprSymFn fn1) == (SomeExprSymFn fn2) =+    case W4.testEquality (W4.symFnId fn1) (W4.symFnId fn2) of+      Just _ -> True+      _ -> False++instance Ord (SomeExprSymFn t) where+  compare (SomeExprSymFn fn1) (SomeExprSymFn fn2) =+    compare (Nonce.indexValue $ W4.symFnId fn1) (Nonce.indexValue $ W4.symFnId fn2) +++instance Show (SomeExprSymFn t) where+  show (SomeExprSymFn f) = show f+++type VarNameEnv t = OMap (Some (W4.ExprBoundVar t)) Text+type FnNameEnv t  = OMap (SomeExprSymFn t) Text++ppFreeVarEnv :: VarNameEnv t -> String+ppFreeVarEnv env = show $ map toStr entries+  where entries = OMap.toAscList env+        toStr :: ((Some (W4.ExprBoundVar t)), Text) -> (String, String, String)+        toStr ((Some var), newName) = ( T.unpack $ W4.solverSymbolAsText $ W4.bvarName var+                                      , show $ W4.bvarType var+                                      , T.unpack newName+                                      )++ppFreeSymFnEnv :: FnNameEnv t -> String+ppFreeSymFnEnv env = show $ map toStr entries+  where entries = OMap.toAscList env+        toStr :: ((SomeExprSymFn t), Text) -> (String, String, String)+        toStr ((SomeExprSymFn fn), newName) = ( T.unpack $ W4.solverSymbolAsText $ W4.symFnName fn+                                              , show $ W4.symFnArgTypes fn+                                              , T.unpack newName+                                              )++-- | Controls how expressions and functions are serialized.+data Config =+  Config+  { cfgAllowFreeVars :: Bool+  -- ^ When @True@, any free What4 @ExprBoundVar@+  -- encountered is simply serialized with a unique name,+  -- and the mapping from What4 ExprBoundVar to unique names+  -- is returned after serialization. When False, an error+  -- is raised when a "free" @ExprBoundVar@ is encountered.+  , cfgAllowFreeSymFns :: Bool+  -- ^ When @True@, any encountered What4 @ExprSymFn@ during+  -- serialization is simply assigned a unique name and the+  -- mapping from What4 ExprSymFn to unique name is returned+  -- after serialization. When @False, encountered+  -- ExprSymFns are serialized at the top level of the+  -- expression in a `(letfn ([f ...]) ...)`.+  }++data Result t =+  Result+  { resSExpr :: S.WellFormedSExpr Atom+  -- ^ The serialized term.+  , resFreeVarEnv :: VarNameEnv t+  -- ^ The free BoundVars that were encountered during+  -- serialization and their associated fresh name+  -- that was used to generate the s-expression.+  , resSymFnEnv :: FnNameEnv t+  -- ^ The SymFns that were encountered during serialization+  -- and their associated fresh name that was used to+  -- generate the s-expression.+  }+++defaultConfig :: Config+defaultConfig = Config { cfgAllowFreeVars = False, cfgAllowFreeSymFns = False}++-- This file is organized top-down, i.e., from high-level to low-level.++-- | Serialize a What4 Expr as a well-formed s-expression+-- (i.e., one which contains no improper lists). Equivalent+-- to @(resSExpr (serializeExpr' defaultConfig))@. Sharing+-- in the AST is achieved via a top-level let-binding around+-- the emitted expression (unless there are no terms with+-- non-atomic terms which can be shared).+serializeExpr :: W4.Expr t tp -> SExpr+serializeExpr = resSExpr . (serializeExprWithConfig defaultConfig)++-- | Serialize a What4 Expr as a well-formed s-expression+-- (i.e., one which contains no improper lists) according to+-- the configuration. Sharing in the AST is achieved via a+-- top-level let-binding around the emitted expression+-- (unless there are no terms with non-atomic terms which+-- can be shared).+serializeExprWithConfig :: Config -> W4.Expr t tp -> Result t+serializeExprWithConfig cfg expr = serializeSomething cfg (convertExprWithLet expr)++-- | Serialize a What4 ExprSymFn as a well-formed+-- s-expression (i.e., one which contains no improper+-- lists). Equivalent to @(resSExpr (serializeSymFn'+-- defaultConfig))@. Sharing in the AST is achieved via a+-- top-level let-binding around the emitted expression+-- (unless there are no terms with non-atomic terms which+-- can be shared).+serializeSymFn :: W4.ExprSymFn t dom ret -> SExpr+serializeSymFn = resSExpr . (serializeSymFnWithConfig defaultConfig)+++-- | Serialize a What4 ExprSymFn as a well-formed+-- s-expression (i.e., one which contains no improper lists)+-- according to the configuration. Sharing in the AST is+-- achieved via a top-level let-binding around the emitted+-- expression (unless there are no terms with non-atomic+-- terms which can be shared).+serializeSymFnWithConfig :: Config -> W4.ExprSymFn t dom ret -> Result t+serializeSymFnWithConfig cfg fn = serializeSomething cfg (convertSymFn fn)++-- | Run the given Memo computation to produce a well-formed+-- s-expression (i.e., one which contains no improper lists)+-- according to the configuration. Sharing in the AST is+-- achieved via a top-level let-binding around the emitted+-- expression (unless there are no terms with non-atomic+-- terms which can be shared).+serializeSomething :: Config -> Memo t SExpr -> Result t+serializeSomething cfg something =+  let (maybeLetFn, getFreeSymFnEnv) = if cfgAllowFreeSymFns cfg+                                      then (return, envFreeSymFnEnv)+                                      else (letFn, \_ -> OMap.empty)+      (sexpr, menv) = runMemo cfg $ something >>= maybeLetFn+      letBindings = map (\(varName, boundExpr) -> S.L [ ident varName, boundExpr ])+                    $ map snd+                    $ OMap.assocs+                    $ envLetBindings menv+      res = mkLet letBindings sexpr+    in Result { resSExpr = res+              , resFreeVarEnv = envFreeVarEnv menv+              , resSymFnEnv = getFreeSymFnEnv menv+              }+++serializeBaseType :: BaseTypeRepr tp -> SExpr+serializeBaseType bt = convertBaseType bt++data MemoEnv t =+  MemoEnv+  { envConfig :: !Config+  -- ^ User provided configuration for serialization.+  , envIdCounter :: !Natural+  -- ^ Internal counter for generating fresh names+  , envLetBindings :: !(OMap SKey (Text, SExpr))+  -- ^ Mapping from What4 expression nonces to the+  -- corresponding let-variable name (the @fst@) and the+  -- corresponding bound term (the @snd@).+  , envFreeVarEnv :: !(VarNameEnv t)+  -- ^ Mapping from What4 ExprBoundVar to the fresh names+  -- assigned to them for serialization purposes.+  , envFreeSymFnEnv :: !(FnNameEnv t)+  -- ^ Mapping from What4 ExprSymFn to the fresh names+  -- assigned to them for serialization purposes.+  , envBoundVars :: Set (Some (W4.ExprBoundVar t))+  -- ^ Set of currently in-scope What4 ExprBoundVars (i.e.,+  -- ExprBoundVars for whom we are serializing the body of+  -- their binding form).+  }++initEnv :: forall t . Config -> MemoEnv t+initEnv cfg = MemoEnv { envConfig = cfg+                      , envIdCounter = 0+                      , envLetBindings = OMap.empty+                      , envFreeVarEnv = OMap.empty+                      , envFreeSymFnEnv = OMap.empty+                      , envBoundVars = Set.empty+                      }++type Memo t a = State (MemoEnv t) a++runMemo :: Config -> (Memo t a) -> (a, MemoEnv t)+runMemo cfg m = MS.runState m $ initEnv cfg+++-- | Serialize the given sexpression within a @letfn@ which+-- serializes and binds all of the encountered SymFns. Note:+-- this recursively also discovers and then serializes+-- SymFns referenced within the body of the SymFns+-- encountered thus far.+letFn :: SExpr -> Memo t SExpr+letFn sexpr = go [] [] Set.empty+  where+    go :: [((SomeExprSymFn t), Text)] -> [(Text, SExpr)] -> Set Text ->  Memo t SExpr+    go [] fnBindings seen = do+      -- Although the `todo` list is empty, we may have+      -- encountered some SymFns along the way, so check for+      -- those and serialize any previously unseen SymFns.+      newFns <- MS.gets (filter (\(_symFn, varName) -> not $ Set.member varName seen)+                         . OMap.assocs+                         . envFreeSymFnEnv)+      if null newFns+        then if null fnBindings+             then return sexpr+             else let bs = map (\(name, def) -> S.L [ident name, def]) fnBindings+                  in return $ S.L [ident "letfn" , S.L bs, sexpr]+        else go newFns fnBindings seen+    go (((SomeExprSymFn nextFn), nextFnName):todo) fnBindings seen = do+      nextSExpr <- convertSymFn nextFn+      let fnBindings' = (nextFnName, nextSExpr):fnBindings+          seen' = Set.insert nextFnName seen+      go todo fnBindings' seen'+++-- | Converts the given What4 expression into an+-- s-expression and clears the let-binding cache (since it+-- just emitted a let binding with any necessary let-bound+-- vars).+convertExprWithLet :: W4.Expr t tp -> Memo t SExpr+convertExprWithLet expr = do+  b <- convertExpr expr+  bs <- map (\(varName, boundExpr) -> S.L [ ident varName, boundExpr ])+        <$> map snd+        <$> OMap.assocs+        <$> MS.gets envLetBindings+  MS.modify' (\r -> r {envLetBindings = OMap.empty})+  return $ mkLet bs b++mkLet :: [SExpr] -> SExpr -> SExpr+mkLet [] body       = body+mkLet bindings body = S.L [ident "let", S.L bindings, body]+++-- | Converts a What4 ExprSymFn into an s-expression within+-- the Memo monad (i.e., no @let@ or @letfn@s are emitted).+convertSymFn :: forall t args ret+              . W4.ExprSymFn t args ret+             -> Memo t SExpr+convertSymFn symFn@(W4.ExprSymFn _ symFnName symFnInfo _) = do+ case symFnInfo of+   W4.DefinedFnInfo argVars body _ -> do+     let sArgTs = convertBaseTypes (W4.fnArgTypes symFn)+         sRetT = convertBaseType (W4.fnReturnType symFn)+     argsWithFreshNames <- let rawArgs = FC.toListFC Some argVars+                           in mapM getBoundVarWithFreshName rawArgs+     let (origBoundVars, freshArgNames) = unzip argsWithFreshNames+     -- Convert the body with the bound variable set and+     -- free-variable mapping extended to reflect being+     -- under the function's binders.+     sExpr <- MS.withState (\ms -> let boundVars = envBoundVars ms+                                       fvEnv = envFreeVarEnv ms+                             in ms { envBoundVars = Set.union boundVars (Set.fromList origBoundVars)+                                   , envFreeVarEnv = fvEnv OMap.<>| (OMap.fromList argsWithFreshNames)})+              $ convertExprWithLet body+     return $ S.L [ ident "definedfn"+                  , string (Some W4.UnicodeRepr) $ W4.solverSymbolAsText symFnName+                  , S.L ((ident "->"):sArgTs ++ [sRetT])+                  , S.L $ map ident freshArgNames+                  , sExpr+                  ]+   W4.UninterpFnInfo argTs retT ->+     let sArgTs = convertBaseTypes argTs+         sRetT = convertBaseType retT+     in return $ S.L [ ident "uninterpfn"+                     , string (Some W4.UnicodeRepr) $ W4.solverSymbolAsText symFnName+                     , S.L ((ident "->"):sArgTs ++ [sRetT])+                     ]+   W4.MatlabSolverFnInfo _msfn _argTs _body ->+     error "MatlabSolverFnInfo SymFns are not yet supported"+  where+    getBoundVarWithFreshName ::+      (Some (W4.ExprBoundVar t)) ->+      Memo t (Some (W4.ExprBoundVar t), Text)+    getBoundVarWithFreshName someVar@(Some var) = do+      nm <- freshName (W4.bvarType var)+      return (someVar, nm)+++-- | Key for sharing SExpr construction. Internally indexes are expression nonces,+-- but the let-binding identifiers are based on insertion order to the OMap+newtype SKey = SKey {sKeyValue :: Word64}+  deriving (Eq, Ord, Show)++++freshName :: W4.BaseTypeRepr tp -> Memo t Text+freshName tp = do+  idCount <- MS.gets envIdCounter+  MS.modify' $ (\e -> e {envIdCounter = idCount + 1})+  let prefix = case tp of+                 W4.BaseBoolRepr{} -> "bool"+                 W4.BaseIntegerRepr{} -> "int"+                 W4.BaseRealRepr{} -> "real"+                 W4.BaseFloatRepr{} -> "fl"+                 W4.BaseStringRepr{} -> "str"+                 W4.BaseComplexRepr -> "cmplx"+                 W4.BaseBVRepr{} -> "bv"+                 W4.BaseStructRepr{} -> "struct"+                 W4.BaseArrayRepr{} -> "arr"+  return $ T.pack $ prefix++(show $ idCount)++freshFnName :: W4.ExprSymFn t args ret -> Memo t Text+freshFnName fn = do+  idCount <- MS.gets envIdCounter+  MS.modify' $ (\e -> e {envIdCounter = idCount + 1})+  let prefix = case W4.symFnInfo fn of +                 W4.UninterpFnInfo{} -> "ufn"+                 W4.DefinedFnInfo{} -> "dfn"+                 W4.MatlabSolverFnInfo{} -> "mfn"+  return $ T.pack $ prefix++(show $ idCount)++++exprSKey :: W4.Expr t tp -> Maybe SKey+exprSKey x = SKey . Nonce.indexValue <$> W4.exprMaybeId x++-- | Allocate a fresh variable for the given+-- nonce-key/s-expression and save the variable/expression+-- mapping in the Memo monad.+addLetBinding :: SKey -> SExpr -> W4.BaseTypeRepr tp -> Memo t Text+addLetBinding key sexp tp = do+  letVarName <- freshName tp+  curLetBindings <- MS.gets envLetBindings+  MS.modify' $ (\e -> e {envLetBindings =  curLetBindings OMap.|> (key, (letVarName, sexp))})+  return letVarName++-- | Converts a What 4 expression into an s-expression+-- within the Memo monad (i.e., no @let@ or @letfn@s are+-- emitted in the result).+convertExpr :: forall t tp . W4.Expr t tp -> Memo t SExpr+convertExpr initialExpr = do+  case exprSKey initialExpr of+    Nothing -> go initialExpr+    Just key -> do+      letCache <- MS.gets envLetBindings+      case OMap.lookup key letCache of+        Just (name, _) -> return $ ident name+        Nothing -> do+          sexp <- go initialExpr+          case sexp of+            S.A _ -> return sexp -- Don't memoize atomic s-expressions - that's just silly.+            _ -> do +              letVarName <- addLetBinding key sexp (W4.exprType initialExpr)+              return $ ident letVarName+  where go :: W4.Expr t tp -> Memo t SExpr+        go (W4.SemiRingLiteral W4.SemiRingIntegerRepr val _) = return $ int val -- do we need/want these?+        go (W4.SemiRingLiteral W4.SemiRingRealRepr val _) = return $ real val+        go (W4.SemiRingLiteral (W4.SemiRingBVRepr _ sz) val _) = return $ bitvec (natValue sz) (BV.asUnsigned val)+        go (W4.StringExpr str _) =+          case (W4.stringLiteralInfo str) of+            W4.UnicodeRepr -> return $ string (Some W4.UnicodeRepr) (W4S.fromUnicodeLit str)+            W4.Char8Repr -> return $ string (Some W4.Char8Repr) $ T.decodeUtf8 $ W4S.fromChar8Lit str+            W4.Char16Repr -> error "Char16 strings are not yet supported"+              -- TODO - there is no `W4S.toLEByteString` currently... hmm...+              -- return $ string (Some W4.Char16Repr) $ T.decodeUtf16LE $ W4S.toLEByteString $ W4S.fromChar16Lit str+        go (W4.FloatExpr prec bf _) = return $ float prec bf+        go (W4.BoolExpr b _) = return $ bool b+        go (W4.AppExpr appExpr) = convertAppExpr' appExpr+        go (W4.NonceAppExpr nae) =+          case W4.nonceExprApp nae of+            W4.FnApp fn args -> convertFnApp fn args+            W4.Forall {} -> error "Forall NonceAppExpr not yet supported"+            W4.Exists {} -> error "Exists NonceAppExpr not yet supported"+            W4.ArrayFromFn {} -> error "ArrayFromFn NonceAppExpr not yet supported"+            W4.MapOverArrays {} -> error "MapOverArrays NonceAppExpr not yet supported"+            W4.ArrayTrueOnEntries {} -> error "ArrayTrueOnEntries NonceAppExpr not yet supported"+            W4.Annotation {} -> error "Annotation NonceAppExpr not yet supported"+        go (W4.BoundVarExpr var) = convertBoundVarExpr var++-- | Serialize bound variables as the s-expression identifier `name_nonce`. This allows us to+-- preserve their human-readable name while ensuring they are globally unique w/ the nonce suffix.+convertBoundVarExpr :: forall t tp. W4.ExprBoundVar t tp -> Memo t SExpr+convertBoundVarExpr x = do+  fvsAllowed <- MS.gets (cfgAllowFreeVars . envConfig)+  bvs <- MS.gets envBoundVars+  -- If this variable is not bound (in the standard syntactic sense)+  -- and free variables are not explicitly permitted, raise an error.+  MS.when ((not $ Set.member (Some x) bvs) && (not fvsAllowed)) $+    error $+    "encountered the free What4 ExprBoundVar `"+    ++ (T.unpack (W4.solverSymbolAsText (W4.bvarName x)))+    ++ "`, but the user-specified configuration dissallows free variables."+  -- Get the renaming cache and either use the name already generated+  -- or generate a fresh name and record it.+  varEnv <- MS.gets envFreeVarEnv+  case OMap.lookup (Some x) varEnv of+    Just var -> return $ ident var+    Nothing -> do+      varName <- freshName $ W4.bvarType x+      MS.modify' $ (\e -> e {envFreeVarEnv = varEnv OMap.|> ((Some x), varName)})+      return $ ident varName+++convertAppExpr' :: forall t tp . W4.AppExpr t tp -> Memo t SExpr+convertAppExpr' = go . W4.appExprApp+  where go :: forall tp' . W4.App (W4.Expr t) tp' -> Memo t SExpr+        go (W4.BaseIte _bt _ e1 e2 e3) = do+          s1 <- goE e1+          s2 <- goE e2+          s3 <- goE e3+          return $ S.L [ident "ite", s1, s2, s3]+        go (W4.BaseEq _bt e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "=", s1, s2]+        go (W4.NotPred e) = do+          s <- goE e+          return $ S.L [ident "notp", s]+        go (W4.ConjPred bm) = convertBoolMap "andp" True bm+        go (W4.BVSlt e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvslt", s1, s2]+        go (W4.BVUlt e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvult", s1, s2]+        go (W4.BVConcat _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "concat", s1, s2]+        go (W4.BVSelect idx n bv) = extract i j bv+          -- See SemMC.Formula.Parser.readExtract for the explanation behind+          -- these values.+          where i = intValue n + j - 1+                j = intValue idx++        -- Note that because the SemiRing has an identity element that+        -- always gets applied, resulting in lots of additional,+        -- unnecessary elements like: "(bvand #xffffffff TERM)".+        -- These will get manifested in the stored form (but generally+        -- _not_ via DSL-generated versions since they don't output+        -- via Printer) and result in longer stored forms.  They could+        -- be eliminated by checking for the identity (e.g. "if mul ==+        -- SR.one (WSum.sumRepr sm)") but the re-loaded representation+        -- will still use the SemiRing, so it's probably not worth the+        -- effort to reduce these.+        go (W4.SemiRingSum sm) =+          case WSum.sumRepr sm of+            W4.SemiRingBVRepr W4.BVArithRepr w ->+              let smul mul e = do+                    s <- goE e+                    return $ S.L [ ident "bvmul", bitvec (natValue w) (BV.asUnsigned mul), s]+                  sval v = return $ bitvec (natValue w) (BV.asUnsigned v)+                  add x y = return $ S.L [ ident "bvadd", x, y ]+              in WSum.evalM add smul sval sm+            W4.SemiRingBVRepr W4.BVBitsRepr w ->+              let smul mul e = do+                    s <- goE e+                    return $ S.L [ ident "bvand", bitvec (natValue w) (BV.asUnsigned mul), s]+                  sval v = return $ bitvec (natValue w) (BV.asUnsigned v)+                  add x y = let op = ident "bvxor" in return $ S.L [ op, x, y ]+              in WSum.evalM add smul sval sm+            W4.SemiRingIntegerRepr ->+              let smul mul e = do+                    s <- goE e+                    return $ S.L [ ident "intmul", int mul, s]+                  sval v = return $ int v+                  add x y = return $ S.L [ ident "intadd", x, y ]+              in WSum.evalM add smul sval sm+            W4.SemiRingRealRepr    -> error "SemiRingSum RealRepr not supported"++        go (W4.SemiRingProd pd) =+          case WSum.prodRepr pd of+            W4.SemiRingBVRepr W4.BVArithRepr w -> do+              let pmul x y = return $ S.L [ ident "bvmul", x, y ]+              maybeS <- WSum.prodEvalM pmul goE pd+              case maybeS of+                Just s -> return s+                Nothing -> return $ bitvec (natValue w) 1+            W4.SemiRingBVRepr W4.BVBitsRepr w -> do+              let pmul x y = return $ S.L [ ident "bvand", x, y ]+              maybeS <- WSum.prodEvalM pmul goE pd+              case maybeS of+                Just s -> return s+                Nothing -> return $ bitvec (natValue w) 1+            W4.SemiRingIntegerRepr -> do+              let pmul x y = return $ S.L [ ident "intmul", x, y ]+              maybeS <- WSum.prodEvalM pmul goE pd+              case maybeS of+                Just s -> return s+                Nothing -> return $ int 1+            W4.SemiRingRealRepr    -> error "convertApp W4.SemiRingProd Real unsupported"++        go (W4.SemiRingLe sr e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          case sr of+            W4.OrderedSemiRingIntegerRepr -> do+              return $ S.L [ ident "intle", s1, s2]+            W4.OrderedSemiRingRealRepr -> error $ "Printer: SemiRingLe is not supported for reals"++        go (W4.BVOrBits width bs) = do+          let op = ident "bvor"+          case W4.bvOrToList bs of+            [] -> return $ bitvec (NR.natValue width) 0+            (x:xs) -> do+              e <- goE x+              let f = (\acc b -> do+                          b' <- goE b+                          return $ S.L [op, b', acc])+              M.foldM f e xs+        go (W4.BVUdiv _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvudiv", s1, s2]+        go (W4.BVUrem _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvurem", s1, s2]+        go (W4.BVSdiv _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvsdiv", s1, s2]+        go (W4.BVSrem _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvsrem", s1, s2]+        go (W4.BVShl _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvshl", s1, s2]+        go (W4.BVLshr _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvlshr", s1, s2]+        go (W4.BVAshr _ e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "bvashr", s1, s2]+        go (W4.BVZext r e) = extend "zero" (intValue r) e+        go (W4.BVSext r e) = extend "sign" (intValue r) e+        go (W4.BVFill r e) = do+          s <- goE e+          return $ S.L [ S.L [ident "_", ident "bvfill", int (intValue r)]+                       , s+                       ]++        go (W4.BVToInteger e) = do+          s <- goE e+          return $ S.L [ident "bvToInteger", s]++        go (W4.SBVToInteger e) = do+          s <- goE e+          return $ S.L [ident "sbvToInteger", s]++        go (W4.FloatNeg _repr e) = do+          s <- goE e+          return $ S.L [ident "floatneg", s]+        go (W4.FloatAbs _repr e) = do+          s <- goE e+          return $ S.L [ident "floatabs", s]++        go (W4.IntDiv e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "intdiv", s1, s2]+        go (W4.IntMod e1 e2) = do+          s1 <- goE e1+          s2 <- goE e2+          return $ S.L [ident "intmod", s1, s2]+        go (W4.IntAbs e1) = do+          s1 <- goE e1+          return $ S.L [ident "intabs", s1]+        go (W4.IntegerToBV e wRepr)  = do+          s <- goE e+          return $ S.L [ident "integerToBV"+                        , nat $ natValue wRepr+                        , s]++        go (W4.StructCtor _tps es) = do+          ss <- convertExprAssignment es+          return $ S.L [ident "struct", S.L ss]+        go (W4.StructField e ix _fieldTp) = do+          s <- goE e+          return $ S.L [ident "field"+                        , s+                        , int $ toInteger $ Ctx.indexVal ix+                        ]++        go (W4.UpdateArray _ _ e1 es e2) = do+          s1 <- goE e1+          ss <- convertExprAssignment es+          s2 <- goE e2+          case ss of+            [idx] -> return $ S.L [ ident "updateArray", s1, idx, s2]+            _ -> error $ "multidimensional arrays not supported"+        go (W4.SelectArray _ e es) = do+          s <- goE e+          ss <- convertExprAssignment es+          case ss of+            [idx] -> return $ S.L [ ident "select", s, idx]+            _ -> error $ "multidimensional arrays not supported"++        go (W4.ArrayMap _idxReprs _resRepr updateMap arr) = do+          updates <- mapM convertArrayUpdate (WAU.toList updateMap)+          arr' <- goE arr+          return $ S.L [ ident "arrayMap"+                       , S.L updates+                       , arr'+                       ]++        go app = error $ "unhandled App: " ++ show app++        convertArrayUpdate :: forall tp1 ctx . (Ctx.Assignment WIL.IndexLit ctx, W4.Expr t tp1) -> Memo t SExpr+        convertArrayUpdate (idxLits, e) = do+          e' <- goE e+          return $ S.L [ S.L (FC.toListFC convertIndexLit idxLits)+                       , e'+                       ]++        -- -- -- -- Helper functions! -- -- -- --+        +        goE :: forall tp' . W4.Expr t tp' -> Memo t SExpr+        goE = convertExpr++        extend :: forall w. Text -> Integer -> W4.Expr t (BaseBVType w) -> Memo t SExpr+        extend op r e = do+          let w = case W4.exprType e of BaseBVRepr len -> intValue len+              extension = r - w+          s <- goE e+          return $ S.L [ S.L [ ident "_", ident $ op <> "_extend", int extension ]+                        , s+                        ]++        extract :: forall tp'. Integer -> Integer -> W4.Expr t tp' -> Memo t SExpr+        extract i j bv = do+          s <- goE bv+          return $ S.L [ S.L [ ident "_", ident "extract", int i, int j ]+                        , s+                        ]++        convertBoolMap :: Text -> Bool -> BooM.BoolMap (W4.Expr t) -> Memo t SExpr+        convertBoolMap op base bm =+          let strBase b = if b+                          then S.L [ident "=", bitvec 1 0, bitvec 1 0]  -- true+                          else S.L [ident "=", bitvec 1 0, bitvec 1 1]  -- false+              strNotBase = strBase . not+          in case BooM.viewBoolMap bm of+               BooM.BoolMapUnit -> return $ strBase base+               BooM.BoolMapDualUnit -> return $ strNotBase base+               BooM.BoolMapTerms ts ->+                 let onEach e r = do+                       s <- arg e+                       return $ S.L [ident op, s, r]+                     arg (t, BooM.Positive) = goE t+                     arg (t, BooM.Negative) = do+                       s <- goE t+                       return $ S.L [ident "notp", s]+                 in F.foldrM onEach (strBase base) ts++convertIndexLit :: WIL.IndexLit tp -> SExpr+convertIndexLit il =+  case il of+    WIL.IntIndexLit iidx -> int iidx+    WIL.BVIndexLit irep bvidx -> bitvec (natValue irep) (BV.asUnsigned bvidx)++convertExprAssignment ::+  Ctx.Assignment (W4.Expr t) sh+  -> Memo t [SExpr]+convertExprAssignment es =+  mapM (\(Some e) -> convertExpr e) (FC.toListFC Some es)++convertFnApp ::+  W4.ExprSymFn t args ret+  -> Ctx.Assignment (W4.Expr t) args+  -> Memo t SExpr+convertFnApp fn args = do+  argSExprs <- convertExprAssignment args+  fnEnv <- MS.gets envFreeSymFnEnv+  case OMap.lookup (SomeExprSymFn fn) fnEnv of+    Just fnName ->+      return $ S.L $ (ident "call"):(ident fnName):argSExprs+    Nothing -> do+      varName <- freshFnName fn+      MS.modify' $ (\e -> e {envFreeSymFnEnv =  fnEnv OMap.|> ((SomeExprSymFn fn), varName)})+      return $ S.L $ (ident "call"):(ident varName):argSExprs+++convertBaseType :: BaseTypeRepr tp -> SExpr+convertBaseType tp = case tp of+  W4.BaseBoolRepr -> S.A $ AId "Bool"+  W4.BaseIntegerRepr -> S.A $ AId "Int"+  W4.BaseRealRepr -> S.A $ AId "Real"+  W4.BaseStringRepr si -> S.L [S.A $ AId "String", convertStringInfo si]+  W4.BaseComplexRepr -> S.A $ AId "Complex"+  W4.BaseBVRepr wRepr -> S.L [S.A (AId "BV"), S.A (AInt (NR.intValue wRepr)) ]+  W4.BaseStructRepr tps -> S.L [ S.A (AId "Struct"), S.L (convertBaseTypes tps) ]+  W4.BaseArrayRepr ixs repr -> S.L [S.A (AId "Array"), S.L $ convertBaseTypes ixs , convertBaseType repr]+  W4.BaseFloatRepr (W4.FloatingPointPrecisionRepr eRepr sRepr) ->+    S.L [ S.A (AId "Float"), S.A (AInt (NR.intValue eRepr)), S.A (AInt (NR.intValue sRepr)) ]++++convertStringInfo :: StringInfoRepr si -> SExpr+convertStringInfo W4.Char8Repr = ident "Char8"+convertStringInfo W4.Char16Repr = ident "Char16"+convertStringInfo W4.UnicodeRepr = ident "Unicode"++-- | Convert an Assignment of base types into a list of base+-- types SExpr, where the left-to-right syntactic ordering+-- of the types is maintained.+convertBaseTypes ::+  Ctx.Assignment BaseTypeRepr tps+  -> [SExpr]+convertBaseTypes asn = FC.toListFC convertBaseType asn
+ src/What4/Serialize/SETokens.hs view
@@ -0,0 +1,259 @@+-- | Definition of the S-Expression tokens used to+-- (de)serialize What4 expressions.++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module What4.Serialize.SETokens+    ( Atom(..)+    , string, ident, int, nat, bitvec, bool, real, float+    , string', ident'+    , printAtom+    , printSExpr+    , parseSExpr+    )+    where++import qualified Data.Foldable as F+import qualified Data.Parameterized.NatRepr as PN+import qualified Data.SCargot as SC+import qualified Data.SCargot.Comments as SC+import qualified Data.SCargot.Repr as SC+import qualified Data.SCargot.Repr.WellFormed as SC+import           Data.Semigroup+import qualified Data.Sequence as Seq+import           Data.Text (Text)+import qualified Data.Text as T+import qualified LibBF as BF+import           Numeric.Natural ( Natural )+import qualified Text.Parsec as P+import           Text.Parsec.Text ( Parser )+import           Text.Printf ( printf )+import           Data.Ratio++import           Data.Parameterized.Some ( Some(..))+import qualified What4.BaseTypes as W4++import           Prelude++data Atom =+  AId Text+  -- ^ An identifier.+  | AStr (Some W4.StringInfoRepr) Text+  -- ^ A prefix followed by a string literal+  -- (.e.g, AStr "u" "Hello World" is serialize as `#u"Hello World"`).+  | AInt Integer+  -- ^ Integer (i.e., unbounded) literal.+  | ANat Natural+  -- ^ Natural (i.e., unbounded) literal+  | AReal Rational+  -- ^ Real (i.e., unbounded) literal.+  | AFloat (Some W4.FloatPrecisionRepr) BF.BigFloat+  -- ^ A floating point literal (with precision)+  | ABV Int Integer+  -- ^ Bitvector, width and then value.+  | ABool Bool+  -- ^ Boolean literal.+  deriving (Show, Eq, Ord)++type SExpr = SC.WellFormedSExpr Atom++string :: Some W4.StringInfoRepr -> Text -> SExpr+string strInfo str = SC.A $ AStr strInfo str++string' :: Some W4.StringInfoRepr -> String -> SExpr+string' strInfo str = SC.A $ AStr strInfo (T.pack str)++-- | Lift an unquoted identifier.+ident :: Text -> SExpr+ident = SC.A . AId++ident' :: String -> SExpr+ident' = SC.A . AId . T.pack++-- | Lift an integer.+int :: Integer -> SExpr+int = SC.A . AInt++-- | Lift a natural+nat :: Natural -> SExpr+nat = SC.A . ANat++-- | Lift a real+real :: Rational -> SExpr+real = SC.A . AReal++-- | Lift a float+float :: W4.FloatPrecisionRepr fpp -> BF.BigFloat -> SExpr+float rep bf = SC.A (AFloat (Some rep) bf)++-- | Lift a bitvector.+bitvec :: Natural -> Integer -> SExpr+bitvec w v = SC.A $ ABV (fromEnum w) v++-- | Lift a boolean.+bool :: Bool -> SExpr+bool = SC.A . ABool+++-- * Output of the S-Expression Formula language+++-- | Generates the the S-expression tokens represented by the sexpr+-- argument, preceeded by a list of strings output as comments.+printSExpr :: Seq.Seq String -> SExpr -> T.Text+printSExpr comments sexpr =+  let outputFmt = SC.setIndentAmount 1 $ SC.unconstrainedPrint printAtom+  in formatComment comments <> (SC.encodeOne outputFmt $ SC.fromWellFormed sexpr)+++formatComment :: Seq.Seq String -> T.Text+formatComment c+  | Seq.null c = T.empty+  | otherwise = T.pack $ unlines $ fmap formatLine (F.toList c)+  where+    formatLine l = printf ";; %s" l+++printAtom :: Atom -> T.Text+printAtom a =+  case a of+    AId s -> s+    AStr si s -> (stringInfoToPrefix si)<>"\""<>s<>"\""+    AInt i -> T.pack (show i)+    ANat n -> T.pack $ "#u"++(show n)+    AReal r -> T.pack $ "#r"++(show (numerator r))++"/"++(show (denominator r))+    ABV w val -> formatBV w val+    ABool b -> if b then "#true" else "#false"+    AFloat (Some rep) bf -> formatFloat rep bf++-- | Format a floating point value with no rounding in base 16+formatFloat :: W4.FloatPrecisionRepr fpp -> BF.BigFloat -> T.Text+formatFloat (W4.FloatingPointPrecisionRepr eb sb) bf =+  T.pack (printf "#f#%s#%s#%s" (show eb) (show sb) (BF.bfToString 16 (BF.showFree Nothing) bf))++formatBV :: Int -> Integer -> T.Text+formatBV w val = T.pack (prefix ++ printf fmt val)+  where+    (prefix, fmt)+      | w `rem` 4 == 0 = ("#x", "%0" ++ show (w `div` 4) ++ "x")+      | otherwise = ("#b", "%0" ++ show w ++ "b")+++-- * Input and parse of the S-Expression Formula language++-- | This is only the base-level parsing of atoms.  The full language+-- parsing is handled by the base here and the Parser definitions.++parseId :: Parser Text+parseId = T.pack <$> ((:) <$> first <*> P.many rest)+  where first = P.letter P.<|> P.oneOf "@+-=<>_."+        rest = P.letter P.<|> P.digit P.<|> P.oneOf "+-=<>_."++stringInfoToPrefix :: Some W4.StringInfoRepr -> Text+stringInfoToPrefix (Some W4.Char16Repr) = "#char16"+stringInfoToPrefix (Some W4.Char8Repr) = "#char8"+stringInfoToPrefix (Some W4.UnicodeRepr) = ""+++parseStrInfo :: Parser (Some W4.StringInfoRepr)+parseStrInfo =+  P.try (P.string "#char16" >> return (Some W4.Char16Repr))+  P.<|> P.try (P.string "#char8" >> return (Some W4.Char8Repr))+  P.<|> (return (Some W4.UnicodeRepr))+++parseStr :: Parser (Some W4.StringInfoRepr, Text)+parseStr = do+  prefix <- parseStrInfo+  _ <- P.char '"'+  str <- concat <$> P.many ( do { _ <- P.char '\\'; c <- P.anyChar ; return ['\\',c]} P.<|> P.many1 (P.noneOf ('"':"\\")))+  _ <- P.char '"'+  return $ (prefix, T.pack str)++parseReal :: Parser Rational+parseReal = do+  _ <- P.string "#r"+  n <- (read :: (String -> Integer)) <$> P.many P.digit+  _ <- P.char '/'+  d <- (read :: (String -> Integer)) <$> P.many P.digit+  return $ n % d++parseInt :: Parser Integer+parseInt = do+  (read <$> P.many1 P.digit)+  P.<|> (*(-1)) . read <$> (P.char '-' >> P.many1 P.digit)++++parseNat :: Parser Natural+parseNat = do+  _ <- P.string "#u"+  n <- P.many1 P.digit+  return $ read n++parseBool :: Parser Bool+parseBool = do +  (P.try (P.string "#false" *> return False))+  P.<|> (P.string "#true" *> return True)+  +parseBV :: Parser (Int, Integer)+parseBV = P.char '#' >> ((P.char 'b' >> parseBin) P.<|> (P.char 'x' >> parseHex))+  where parseBin = P.oneOf "10" >>= \d -> parseBin' (1, if d == '1' then 1 else 0)++        parseBin' :: (Int, Integer) -> Parser (Int, Integer)+        parseBin' (bits, x) = do+          P.optionMaybe (P.oneOf "10") >>= \case+            Just d -> parseBin' (bits + 1, x * 2 + (if d == '1' then 1 else 0))+            Nothing -> return (bits, x)++        parseHex = (\s -> (length s * 4, read ("0x" ++ s))) <$> P.many1 P.hexDigit++parseFloat :: Parser (Some W4.FloatPrecisionRepr, BF.BigFloat)+parseFloat = do+  _ <- P.string "#f#"+  -- We printed the nat reprs out in decimal+  eb :: Natural+     <- read <$> P.many1 P.digit+  _ <- P.char '#'+  sb :: Natural+     <- read <$> P.many1 P.digit+  _ <- P.char '#'++  -- The float value itself is printed out as a hex literal+  hexDigits <- P.many1 P.hexDigit++  Some ebRepr <- return (PN.mkNatRepr eb)+  Some sbRepr <- return (PN.mkNatRepr sb)+  case (PN.testLeq (PN.knownNat @2) ebRepr, PN.testLeq (PN.knownNat @2) sbRepr) of+    (Just PN.LeqProof, Just PN.LeqProof) -> do+      let rep = W4.FloatingPointPrecisionRepr ebRepr sbRepr++      -- We know our format: it is determined by the exponent bits (eb) and the+      -- significand bits (sb) parsed above+      let fmt = BF.precBits (fromIntegral sb) <> BF.expBits (fromIntegral eb)+      let (bf, status) = BF.bfFromString 16 fmt hexDigits+      case status of+        BF.Ok -> return (Some rep, bf)+        _ -> P.unexpected ("Error parsing hex float: 0x" ++ hexDigits)+    _ -> P.unexpected ("Invalid exponent or significand size: " ++ show (eb, sb))+++parseAtom :: Parser Atom+parseAtom+  =     P.try (ANat  <$> parseNat)+  P.<|> P.try (uncurry AFloat <$> parseFloat)+  P.<|> P.try (AReal <$> parseReal)+  P.<|> P.try (AInt  <$> parseInt)+  P.<|> P.try (AId   <$> parseId)+  P.<|> P.try (uncurry AStr  <$> parseStr)+  P.<|> P.try (ABool <$> parseBool)+  P.<|> P.try (uncurry ABV <$> parseBV)++parseSExpr :: T.Text -> Either String SExpr+parseSExpr = SC.decodeOne $ SC.asWellFormed $ SC.withLispComments (SC.mkParser parseAtom)
src/What4/Solver.hs view
@@ -53,6 +53,17 @@   , cvc4Options   , cvc4Features +    -- * CVC5+  , CVC5(..)+  , cvc5Adapter+  , cvc5Path+  , cvc5Timeout+  , runCVC5InOverride+  , writeCVC5SMT2File+  , withCVC5+  , cvc5Options+  , cvc5Features+     -- * DReal   , DReal(..)   , DRealBindings@@ -98,6 +109,7 @@ import           What4.Solver.Adapter import           What4.Solver.Boolector import           What4.Solver.CVC4+import           What4.Solver.CVC5 import           What4.Solver.DReal import           What4.Solver.ExternalABC import           What4.Solver.STP
src/What4/Solver/Boolector.hs view
@@ -39,6 +39,7 @@ import           What4.ProblemFeatures import           What4.Protocol.Online import qualified What4.Protocol.SMTLib2 as SMT2+import qualified What4.Protocol.SMTLib2.Syntax as Syntax import           What4.Protocol.SMTLib2.Response ( strictSMTParseOpt ) import           What4.SatResult import           What4.Solver.Adapter@@ -128,7 +129,7 @@   defaultSolverArgs _ _ = return ["--smt2", "--incremental", "--output-format=smt2", "-e=0"]   defaultFeatures _ = boolectorFeatures   setDefaultLogicAndOptions writer = do-    SMT2.setLogic writer SMT2.allSupported+    SMT2.setLogic writer Syntax.allLogic     SMT2.setProduceModels writer True  setInteractiveLogicAndOptions ::@@ -141,7 +142,7 @@     SMT2.setOption writer "global-declarations" "true"     when (SMT2.supportedFeatures writer `hasProblemFeature` useUnsatCores) $ do       SMT2.setOption writer "produce-unsat-cores" "true"-    SMT2.setLogic writer SMT2.allSupported+    SMT2.setLogic writer Syntax.allLogic  instance OnlineSolver (SMT2.Writer Boolector) where   startSolverProcess feat mbIOh sym = do
src/What4/Solver/CVC4.hs view
@@ -78,7 +78,7 @@ cvc4TimeoutOLD :: ConfigOption BaseIntegerType cvc4TimeoutOLD = configOption knownRepr "cvc4_timeout" --- | Control strict parsing for Boolector solver responses (defaults+-- | Control strict parsing for CVC4 solver responses (defaults -- to solver.strict-parsing option setting). cvc4StrictParsing :: ConfigOption BaseBoolType cvc4StrictParsing = configOption knownRepr "solver.cvc4.strict_parsing"@@ -164,7 +164,7 @@                 (getOption =<< getOptionSetting RSP.strictSMTParsing cfg)   c <- SMT2.newWriter CVC4 out_str in_str nullAcknowledgementAction strictness "CVC4"          True cvc4Features True bindings-  SMT2.setLogic c SMT2.allSupported+  SMT2.setLogic c Syntax.allLogic   SMT2.setProduceModels c True   forM_ ps $ SMT2.assume c   SMT2.writeCheckSat c@@ -196,7 +196,7 @@    setDefaultLogicAndOptions writer = do     -- Tell CVC4 to use all supported logics.-    SMT2.setLogic writer SMT2.allSupported+    SMT2.setLogic writer Syntax.allLogic     -- Tell CVC4 to produce models     SMT2.setProduceModels writer True @@ -237,7 +237,7 @@     when (supportedFeatures writer `hasProblemFeature` useUnsatCores) $ do       SMT2.setOption writer "produce-unsat-cores" "true"     -- Tell CVC4 to use all supported logics.-    SMT2.setLogic writer SMT2.allSupported+    SMT2.setLogic writer Syntax.allLogic  instance OnlineSolver (SMT2.Writer CVC4) where   startSolverProcess feat mbIOh sym = do
+ src/What4/Solver/CVC5.hs view
@@ -0,0 +1,380 @@+------------------------------------------------------------------------+-- |+-- Module      : What4.Solver.CVC5+-- Description : Solver adapter code for cvc5+-- Copyright   : (c) Galois, Inc 2022+-- License     : BSD3+-- Maintainer  : Rob Dockins <rdockins@galois.com>+-- 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
src/What4/Solver/Yices.hs view
@@ -331,6 +331,9 @@    fromText t = T (Builder.fromText t) +unsupportedFeature :: String -> a+unsupportedFeature s = error ("Yices does not support " <> s)+ floatFail :: HasCallStack => a floatFail = error "Yices does not support IEEE-754 floating-point numbers" @@ -492,6 +495,8 @@    pushCommand _   = const $ safeCmd "(push)"   popCommand _    = const $ safeCmd "(pop)"+  push2Command _   = unsupportedFeature "(push 2)"+  pop2Command _    = unsupportedFeature "(pop 2)"   resetCommand _  = const $ safeCmd "(reset)"   checkCommands _  =     [ setTimeoutCommand, const $ safeCmd "(check)" ]@@ -502,6 +507,9 @@    getUnsatAssumptionsCommand _ = const $ safeCmd "(show-unsat-assumptions)"   getUnsatCoreCommand _ = const $ safeCmd "(show-unsat-core)"+  getAbductCommand _ _ _ = unsupportedFeature "abduction"+  getAbductNextCommand _ = unsupportedFeature "abduction"+   setOptCommand _ x o = setParamCommand x (Builder.fromText o)    assertCommand _ (T nm) = const $ unsafeCmd $ app "assert" [nm]@@ -520,6 +528,10 @@                  , renderTerm (yicesLambda args t)                  ] +  synthFunCommand _ _ _ _ = unsupportedFeature "SyGuS"+  declareVarCommand _ _ _ = unsupportedFeature "SyGuS"+  constraintCommand _ _ = unsupportedFeature "SyGuS"+   resetDeclaredStructs conn = resetUnitType conn    structProj _n i s = term_app "select" [s, fromIntegral (Ctx.indexVal i + 1)]@@ -589,6 +601,9 @@                  unlines [ "Could not parse unsat core result."                          , "*** Exception: " ++ displayException e                          ]+  smtAbductResult _ _ _ = unsupportedFeature "abduction"++  smtAbductNextResult _ = unsupportedFeature "abduction"   -- | Exceptions that can occur when reading responses from Yices
src/What4/Solver/Z3.hs view
@@ -10,10 +10,11 @@ -- Z3-specific tweaks to the basic SMTLib2 solver interface. ------------------------------------------------------------------------ {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-}-+{-# LANGUAGE TypeOperators #-} {-# LANGUAGE GADTs #-} module What4.Solver.Z3   ( Z3(..)@@ -27,15 +28,21 @@   , runZ3InOverride   , withZ3   , writeZ3SMT2File+  , runZ3Horn+  , writeZ3HornSMT2File   ) where  import           Control.Monad ( when )+import qualified Data.Bimap as Bimap import           Data.Bits+import           Data.Foldable import           Data.String import           Data.Text (Text) import qualified Data.Text as T import           System.IO +import           Data.Parameterized.Map (MapF)+import           Data.Parameterized.Some import           What4.BaseTypes import           What4.Concrete import           What4.Config@@ -46,7 +53,8 @@ import           What4.Protocol.Online import qualified What4.Protocol.SMTLib2 as SMT2 import           What4.Protocol.SMTLib2.Response ( strictSMTParseOpt )-import qualified What4.Protocol.SMTLib2.Syntax as SMT2Syntax+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@@ -139,7 +147,7 @@           fields = field_def <$> [1..n]           decl = app tp [app ctor fields]           decls = "(" <> decl <> ")"-       in SMT2Syntax.Cmd $ app "declare-datatypes" [ params, decls ]+       in Syntax.Cmd $ app "declare-datatypes" [ params, decls ]  z3Features :: ProblemFeatures z3Features = useNonlinearArithmetic@@ -235,3 +243,87 @@       timeout feat (Just z3StrictParsing) mbIOh sym    shutdownSolverProcess = SMT2.shutdownSolver Z3++-- | Check the satisfiability of a set of constrained Horn clauses (CHCs).+--+-- CHCs are represented as pure SMT-LIB2 implications. For more information, see+-- the [Z3 guide](https://microsoft.github.io/z3guide/docs/fixedpoints/intro/).+runZ3Horn ::+  sym ~ ExprBuilder t st fs =>+  sym ->+  LogData ->+  [SomeSymFn sym] ->+  [BoolExpr t] ->+  IO (SatResult (MapF (SymFnWrapper sym) (SymFnWrapper sym)) ())+runZ3Horn sym log_data inv_fns horn_clauses = do+  logSolverEvent sym+    (SolverStartSATQuery $ SolverStartSATQueryRec+      { satQuerySolverName = show Z3+      , satQueryReason = logReason log_data+      })++  path <- SMT2.defaultSolverPath Z3 sym+  withZ3 sym path (log_data { logVerbosity = 2 }) $ \session -> do+    writeHornProblem sym (SMT2.sessionWriter session) inv_fns horn_clauses+    result <- RSP.getLimitedSolverResponse "check-sat"+      (\case+        RSP.AckSat -> Just $ Sat ()+        RSP.AckUnsat -> Just $ Unsat ()+        RSP.AckUnknown -> Just Unknown+        _ -> Nothing)+      (SMT2.sessionWriter session)+      Syntax.checkSat++    logSolverEvent sym+      (SolverEndSATQuery $ SolverEndSATQueryRec+        { satQueryResult = result+        , satQueryError = Nothing+        })++    traverseSatResult+      (\() -> do+        sexp <- RSP.getLimitedSolverResponse "get-value"+          (\case+            RSP.AckSuccessSExp sexp -> Just sexp+            _ -> Nothing)+          (SMT2.sessionWriter session)+          (Syntax.getValue [])+        SMT2.parseFnValues sym (SMT2.sessionWriter session) inv_fns sexp)+      return+      result++writeZ3HornSMT2File ::+  sym ~ ExprBuilder t st fs =>+  sym ->+  Handle ->+  [SomeSymFn sym] ->+  [BoolExpr t] ->+  IO ()+writeZ3HornSMT2File sym h inv_fns horn_clauses = do+  writer <- SMT2.defaultFileWriter+    Z3+    (show Z3)+    (SMT2.defaultFeatures Z3)+    (Just z3StrictParsing)+    sym+    h+  SMT2.setDefaultLogicAndOptions writer+  writeHornProblem sym writer inv_fns horn_clauses+  SMT2.writeExit writer++writeHornProblem ::+  sym ~ ExprBuilder t st fs =>+  sym ->+  WriterConn t (SMT2.Writer Z3) ->+  [SomeSymFn sym] ->+  [BoolExpr t] ->+  IO ()+writeHornProblem sym writer inv_fns horn_clauses = do+  SMT2.setLogic writer Syntax.hornLogic+  implications <- mapM+    (\clause -> foldrM (viewSome $ forallPred sym) clause $ exprUninterpConstants sym clause)+    horn_clauses+  mapM_ (SMT2.assume writer) implications+  SMT2.writeCheckSat writer+  fn_name_bimap <- cacheLookupFnNameBimap writer $ map (\(SomeSymFn fn) -> SomeExprSymFn fn) inv_fns+  SMT2.writeGetValue writer $ map fromText $ Bimap.elems fn_name_bimap
src/What4/Utils/AbstractDomains.hs view
@@ -26,6 +26,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ViewPatterns #-}  module What4.Utils.AbstractDomains
src/What4/Utils/OnlyIntRepr.hs view
@@ -8,6 +8,7 @@ restricting index types in MATLAB arrays. -} {-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeOperators #-} module What4.Utils.OnlyIntRepr   ( OnlyIntRepr(..)   , toBaseTypeRepr
+ src/What4/Utils/Serialize.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE NondecreasingIndentation #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeOperators #-}+module What4.Utils.Serialize+    (+      withRounding+    , makeSymbol+    , asyncLinked+    , withAsyncLinked+    ) where++import qualified Control.Exception as E+import           Text.Printf ( printf )+import qualified Data.BitVector.Sized as BV+import           What4.BaseTypes+import qualified What4.Interface as S+import           What4.Symbol ( SolverSymbol, userSymbol )+++import qualified UnliftIO as U++----------------------------------------------------------------+-- * Async++-- | Fork an async action that is linked to the parent thread, but can+-- be safely 'U.cancel'd without also killing the parent thread.+--+-- Note that if your async doesn't return unit, then you probably want+-- to 'U.wait' for it instead, which eliminates the need for linking+-- it. Also, if you plan to cancel the async near where you fork it,+-- then 'withAsyncLinked' is a better choice than using this function+-- and subsequently canceling, since it ensures cancellation.+--+-- See https://github.com/simonmar/async/issues/25 for a perhaps more+-- robust, but also harder to use version of this. The linked version+-- is harder to use because it requires a special version of @cancel@.+asyncLinked :: (U.MonadUnliftIO m) => m () -> m (U.Async ())+asyncLinked action = do+  -- We use 'U.mask' to avoid a race condition between starting the+  -- async and running @action@. Without 'U.mask' here, an async+  -- exception (e.g. via 'U.cancel') could arrive after+  -- @handleUnliftIO@ starts to run but before @action@ starts.+  U.mask $ \restore -> do+  a <- U.async $ handleUnliftIO threadKilledHandler (restore action)+  restore $ do+  U.link a+  return a++-- | Handle asynchronous 'E.ThreadKilled' exceptions without killing the parent+-- thread. All other forms of asynchronous exceptions are rethrown.+threadKilledHandler :: Monad m => E.AsyncException -> m ()+threadKilledHandler E.ThreadKilled = return ()+threadKilledHandler e              = E.throw e++-- | A version of 'U.withAsync' that safely links the child. See+-- 'asyncLinked'.+withAsyncLinked :: (U.MonadUnliftIO m) => m () -> (U.Async () -> m a) -> m a+withAsyncLinked child parent = do+  U.mask $ \restore -> do+  U.withAsync (handleUnliftIO threadKilledHandler $ restore child) $ \a -> restore $ do+  U.link a+  parent a++-- A 'U.MonadUnliftIO' version of 'Control.Exception.handle'.+--+-- The 'U.handle' doesn't catch async exceptions, because the+-- @unliftio@ library uses the @safe-execeptions@ library, not+-- @base@, for it exception handling primitives. This is very+-- confusing if you're not expecting it!+handleUnliftIO :: (U.MonadUnliftIO m, U.Exception e)+               => (e -> m a) -> m a -> m a+handleUnliftIO h a = U.withUnliftIO $ \u ->+  E.handle (U.unliftIO u . h) (U.unliftIO u a)++-- | Try converting any 'String' into a 'SolverSymbol'. If it is an invalid+-- symbol, then error.+makeSymbol :: String -> SolverSymbol+makeSymbol name = case userSymbol sanitizedName of+                    Right symbol -> symbol+                    Left _ -> error $ printf "tried to create symbol with bad name: %s (%s)"+                                             name sanitizedName+  where+    -- We use a custom name sanitizer here because downstream clients may depend+    -- on the format of the name. It would be nice to use 'safeSymbol' here, but+    -- it mangles names with z-encoding in a way that might be unusable+    -- downstream.+    sanitizedName = map (\c -> case c of ' ' -> '_'; '.' -> '_'; _ -> c) name++withRounding+  :: forall sym tp+   . S.IsExprBuilder sym+  => sym+  -> S.SymBV sym 2+  -> (S.RoundingMode -> IO (S.SymExpr sym tp))+  -> IO (S.SymExpr sym tp)+withRounding sym r action = do+  cRNE <- roundingCond S.RNE+  cRTZ <- roundingCond S.RTZ+  cRTP <- roundingCond S.RTP+  S.iteM S.baseTypeIte sym cRNE+    (action S.RNE) $+    S.iteM S.baseTypeIte sym cRTZ+      (action S.RTZ) $+      S.iteM S.baseTypeIte sym cRTP (action S.RTP) (action S.RTN)+ where+  roundingCond :: S.RoundingMode -> IO (S.Pred sym)+  roundingCond rm =+    S.bvEq sym r =<< S.bvLit sym knownNat (BV.mkBV knownNat (roundingModeToBits rm))++roundingModeToBits :: S.RoundingMode -> Integer+roundingModeToBits = \case+  S.RNE -> 0+  S.RTZ -> 1+  S.RTP -> 2+  S.RTN -> 3+  S.RNA -> error $ "unsupported rounding mode: " ++ show S.RNA
+ test/Abduct.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import           Test.Tasty+import           Test.Tasty.HUnit++import           Data.Foldable (forM_)+import qualified Data.Text as Text+import           Data.Parameterized.Nonce (newIONonceGenerator)+import           Data.Parameterized.Some (Some(..))++import           System.IO (FilePath, IOMode(..), openFile, hClose)+import           System.IO.Temp (withSystemTempFile)+import           What4.Config (extendConfig)+import           What4.Expr+                   ( ExprBuilder,  FloatModeRepr(..), newExprBuilder+                   , BoolExpr, IntegerExpr, GroundValue, groundEval+                   , EmptyExprBuilderState(..))+import           What4.Interface+                   ( BaseTypeRepr(..), getConfiguration+                   , freshConstant, safeSymbol, notPred+                   , impliesPred, intLit, intAdd, intLe )+import           What4.Solver+import           What4.Symbol (SolverSymbol(..))+import           What4.Protocol.SMTLib2 as SMT2+                   (assume, sessionWriter, runCheckSat, runGetAbducts, Writer)+import           What4.Protocol.SMTWriter+                   (mkSMTTerm)+import           What4.Protocol.Online++cvc5executable :: FilePath+cvc5executable = "cvc5"++-- Call the online getAbduct tactic+testGetAbductOnline ::+  ExprBuilder t st fs ->+  [BoolExpr t] ->+  BoolExpr t ->+  Int ->+  IO [String]+testGetAbductOnline sym hs g n = do+  -- Print SMT file in /tmp/+  withSystemTempFile "what4abdonline" $ \fname mirroredOutput -> do+    proc <- startSolverProcess @(SMT2.Writer CVC5) cvc5Features (Just mirroredOutput) sym+    let conn = solverConn proc+    inNewFrame proc $ do+      mapM_ (\x -> assume conn x) hs+      getAbducts proc n (Text.pack "abd") g++-- Call the offline getAbduct tactic+testGetAbductOffline ::+  ExprBuilder t st fs ->+  BoolExpr t ->+  Int ->+  IO [String]+testGetAbductOffline sym f n = do+  -- Print SMT file in /tmp/+  withSystemTempFile "what4abdoffline" $ \fname mirroredOutput -> do+    let logData = LogData { logCallbackVerbose = \_ _ -> return ()+                          , logVerbosity = 2+                          , logReason = "defaultReason"+                          , logHandle = Just mirroredOutput }+    withCVC5 sym cvc5executable logData $ \session -> do+      f_term <- mkSMTTerm (sessionWriter session) f+      runGetAbducts session n (Text.pack "abd") f_term++-- Prove f using an SMT solver, by checking if ~f is unsatisfiable+prove ::+  ExprBuilder t st fs ->+  BoolExpr t ->+  [(String, IntegerExpr t)] ->+  IO (SatResult () ())+prove sym f es = do+  -- Print SMT file in /tmp/+  withSystemTempFile "what4prove" $ \fname mirroredOutput -> do+    proc <- startSolverProcess @(SMT2.Writer CVC5) cvc5Features (Just mirroredOutput) sym+    let logData = LogData { logCallbackVerbose = \_ _ -> return ()+                           , logVerbosity = 2+                           , logReason = "defaultReason"+                           , logHandle = Just mirroredOutput }+    +    -- To prove f, we check whether not f is unsat+    notf <- notPred sym f+    withCVC5 sym cvc5executable logData $ \session -> do+      checkSatisfiable proc "test" notf++-- Tests++testAbdOnline :: ExprBuilder t st fs -> +  [BoolExpr t] -> +  BoolExpr t -> +  TestTree+testAbdOnline sym hs g = testCase "getting 3 abducts using cvc5 online" $ do+  -- Ask for 3 abducts for f+  res <- testGetAbductOnline sym hs g 3+  (length res == 3) @? "3 online abducts"++testAbdOffline :: ExprBuilder t st fs -> +  BoolExpr t -> +  [(String, IntegerExpr t)] -> +  TestTree+testAbdOffline sym f es = testCase "getting 3 abducts using cvc5 offline" $ do+  -- Ask for 3 abducts for f+  res <- testGetAbductOffline sym f 3+  (length res == 3) @? "3 offline abducts"++testSatAbd :: ExprBuilder t st fs -> +  BoolExpr t -> +  [(String, IntegerExpr t)] -> +  TestTree+testSatAbd sym f es = testCase "testing SAT query for abduction" $ do+  -- Prove f (is ~f unsatisfiable?). We expect ~f to be satisfiable+  res <- prove sym f es+  isSat res @? "sat"+++main :: IO ()+main = do+  Some ng <- newIONonceGenerator+  sym <- newExprBuilder FloatIEEERepr EmptyExprBuilderState ng++  -- This line is necessary for working with cvc5.+  extendConfig cvc5Options (getConfiguration sym)++  -- Build this formula: ~(y >= 0 => (x + y + z) >= 0)+  +  -- First, declare fresh constants for each of the three variables x, y, z.+  x <- freshConstant sym (safeSymbol "x") BaseIntegerRepr+  y <- freshConstant sym (safeSymbol "y") BaseIntegerRepr+  z <- freshConstant sym (safeSymbol "z") BaseIntegerRepr++  -- Next, build up the clause+  zero <- intLit sym 0                    -- 0+  pxyz <- intAdd sym x =<< intAdd sym y z -- x + y + z+  ygte0 <- intLe sym zero y               -- 0 <= y+  xyzgte0 <- intLe sym zero pxyz          -- 0 <= (x + y + z) +  f <- impliesPred sym ygte0 xyzgte0      -- (0 <= y) -> (0 <= (x + y + z))++  defaultMain $ testGroup "Tests" $+    [ -- test passes if f is disproved (~f is sat)+      testSatAbd sym f [ ("x", x)+                       , ("y", y)+                       , ("z", z)+                       ],+      -- test passes if cvc5 returns 3 abducts (offline)+      testAbdOffline sym f [ ("x", x)+                           , ("y", y)+                           , ("z", z)+                           ],+      -- test passes if cvc5 returns 3 abducts (online)+      testAbdOnline sym [ygte0] xyzgte0+    ]
test/AdapterTest.hs view
@@ -42,6 +42,7 @@ allAdapters :: [SolverAdapter EmptyExprBuilderState] allAdapters =   [ cvc4Adapter+  , cvc5Adapter   , yicesAdapter   , z3Adapter   , boolectorAdapter
test/ExprBuilderSMTLib2.hs view
@@ -1191,7 +1191,7 @@ main :: IO () main = do   testLevel <- TestLevel . fromMaybe "0" <$> lookupEnv "CI_TEST_LEVEL"-  let solverNames = SolverName <$> [ "cvc4", "yices", "z3" ]+  let solverNames = SolverName <$> [ "cvc4", "cvc5", "yices", "z3" ]   solvers <- reportSolverVersions testLevel id              =<< (zip solverNames <$> mapM getSolverVersion solverNames)   let z3Tests =@@ -1289,6 +1289,7 @@         , testCase "Yices rounding" $ withYices roundingTest         , testCase "Yices #182 test case" $ withYices issue182Test         ]+  let cvc5Tests = cvc4Tests   let skipIfNotPresent nm = if SolverName nm `elem` (fst <$> solvers) then id                             else fmap (ignoreTestBecause (nm <> " not present"))   defaultMain $ testGroup "Tests" $@@ -1309,5 +1310,6 @@     , testUnsafeSetAbstractValue2     ]     <> (skipIfNotPresent "cvc4" cvc4Tests)+    <> (skipIfNotPresent "cvc5" cvc5Tests)     <> (skipIfNotPresent "yices" yicesTests)     <> (skipIfNotPresent "z3" z3Tests)
test/ExprsTest.hs view
@@ -91,7 +91,65 @@           diff nabs (>=) 0         _ -> failure   , testIntDivMod+  , testIntMinMax   ]++testIntMinMax :: TestTree+testIntMinMax = testGroup "int min/max"+  [ testProperty "(j <= c && c <= i) -> intMax j i == intMax i j == i" $+    property $ do+      c <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      liftIO $ withTestSolver $ \sym -> do +        j <- freshBoundedInt sym (safeSymbol "j") Nothing (Just c)+        i <- freshBoundedInt sym (safeSymbol "i") (Just c) Nothing+        max_j_i <- intMax sym j i+        res1 <- intEq sym max_j_i i+        asConstantPred res1 @=? Just True+        max_i_j <- intMax sym i j+        res2 <- intEq sym max_i_j i+        asConstantPred res2 @=? Just True+  , testProperty "(lo_i <= i && lo_j <= j) -> (max lo_j lo_j) <= intMax i j" $+    property $ do+      lo_i <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      lo_j <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      liftIO $ withTestSolver $ \sym -> do+        i <- freshBoundedInt sym (safeSymbol "i") (Just lo_i) Nothing+        j <- freshBoundedInt sym (safeSymbol "j") (Just lo_j) Nothing+        lo <- intLit sym (max lo_i lo_j)+        max_i_j <- intMax sym i j+        res1 <- intLe sym lo max_i_j+        asConstantPred res1 @=? Just True+        max_j_i <- intMax sym j i+        res2 <- intLe sym lo max_j_i+        asConstantPred res2 @=? Just True   +  , testProperty "(i <= c && c <= j) -> intMin j i == intMin i j == i" $+    property $ do+      c <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      liftIO $ withTestSolver $ \sym -> do+        j <- freshBoundedInt sym (safeSymbol "j") (Just c) Nothing+        i <- freshBoundedInt sym (safeSymbol "i") Nothing (Just c)+        min_j_i <- intMin sym j i+        res1 <- intEq sym min_j_i i+        asConstantPred res1 @=? Just True+        min_i_j <- intMin sym i j+        res2 <- intEq sym min_i_j i+        asConstantPred res2 @=? Just True+  , testProperty "(i <= hi_i && j <= hi_j) -> intMin i j <= (min hi_j hi_j)" $+    property $ do+      hi_i <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      hi_j <- forAll $ Gen.integral $ Range.linear (-1000) 1000+      liftIO $ withTestSolver $ \sym -> do+        i <- freshBoundedInt sym (safeSymbol "i") Nothing (Just hi_i)+        j <- freshBoundedInt sym (safeSymbol "j") Nothing (Just hi_j)+        hi <- intLit sym (min hi_i hi_j)+        min_i_j <- intMin sym i j+        res1 <- intLe sym min_i_j hi+        asConstantPred res1 @=? Just True+        min_j_i <- intMin sym j i+        res2 <- intLe sym min_j_i hi+        asConstantPred res2 @=? Just True+  ]+  testIntDivMod :: TestTree testIntDivMod = testGroup "integer division and mod"
+ test/InvariantSynthesis.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}++import           ProbeSolvers+import           Test.Tasty+import           Test.Tasty.ExpectedFailure+import           Test.Tasty.HUnit++import           Data.Maybe+import           System.Environment++import qualified Data.BitVector.Sized as BV+import           Data.Parameterized.Context+import           Data.Parameterized.Map (MapF)+import           Data.Parameterized.Nonce++import           What4.Config+import           What4.Expr+import           What4.Interface+import           What4.SatResult+import           What4.Solver.Adapter+import qualified What4.Solver.CVC5 as CVC5+import qualified What4.Solver.Z3 as Z3++type SimpleExprBuilder t fs = ExprBuilder t EmptyExprBuilderState fs++logData :: LogData+logData = defaultLogData { logCallbackVerbose = (\_ -> putStrLn) }++withSym :: FloatModeRepr fm -> (forall t . SimpleExprBuilder t (Flags fm) -> IO a) -> IO a+withSym float_mode action = withIONonceGenerator $ \gen -> do+  sym <- newExprBuilder float_mode EmptyExprBuilderState gen+  extendConfig CVC5.cvc5Options (getConfiguration sym)+  extendConfig Z3.z3Options (getConfiguration sym)+  action sym++intProblem :: IsSymExprBuilder sym => sym -> IO ([SomeSymFn sym], [Pred sym], Pred sym)+intProblem sym = do+  inv <- freshTotalUninterpFn sym (safeSymbol "inv") knownRepr knownRepr+  i <- freshConstant sym (safeSymbol "i") knownRepr+  n <- freshConstant sym (safeSymbol "n") knownRepr+  zero <- intLit sym 0+  one <- intLit sym 1+  lt_1_n <- intLt sym one n+  inv_0_n <- applySymFn sym inv $ Empty :> zero :> n+  -- 1 < n ==> inv(0, n)+  impl0 <- impliesPred sym lt_1_n inv_0_n+  inv_i_n <- applySymFn sym inv $ Empty :> i :> n+  add_i_1 <- intAdd sym i one+  lt_add_i_1_n <- intLt sym add_i_1 n+  conj0 <- andPred sym inv_i_n lt_add_i_1_n+  inv_add_i_1_n <- applySymFn sym inv $ Empty :> add_i_1 :> n+  -- inv(i, n) /\ i+1 < n ==> inv(i+1, n)+  impl1 <- impliesPred sym conj0 inv_add_i_1_n+  le_0_i <- intLe sym zero i+  lt_i_n <- intLt sym i n+  conj1 <- andPred sym le_0_i lt_i_n+  -- inv(i, n) ==> 0 <= i /\ i < n+  impl2 <- impliesPred sym inv_i_n conj1++  -- inv(i, n) /\ not (i + 1 < n) ==> i + 1 == n+  not_lt_add_i_1_n <- notPred sym lt_add_i_1_n+  conj2 <- andPred sym inv_i_n not_lt_add_i_1_n+  eq_add_i_1_n <- intEq sym add_i_1 n+  impl3 <- notPred sym =<< impliesPred sym conj2 eq_add_i_1_n++  return ([SomeSymFn inv], [impl0, impl1, impl2], impl3)++bvProblem :: IsSymExprBuilder sym => sym -> IO ([SomeSymFn sym], [Pred sym], Pred sym)+bvProblem sym = do+  inv <- freshTotalUninterpFn sym (safeSymbol "inv") knownRepr knownRepr+  i <- freshConstant sym (safeSymbol "i") $ BaseBVRepr $ knownNat @64+  n <- freshConstant sym (safeSymbol "n") knownRepr+  zero <- bvLit sym knownNat $ BV.zero knownNat+  one <- bvLit sym knownNat $ BV.one knownNat+  ult_1_n <- bvUlt sym one n+  inv_0_n <- applySymFn sym inv $ Empty :> zero :> n+  -- 1 < n ==> inv(0, n)+  impl0 <- impliesPred sym ult_1_n inv_0_n+  inv_i_n <- applySymFn sym inv $ Empty :> i :> n+  add_i_1 <- bvAdd sym i one+  ult_add_i_1_n <- bvUlt sym add_i_1 n+  conj0 <- andPred sym inv_i_n ult_add_i_1_n+  inv_add_i_1_n <- applySymFn sym inv $ Empty :> add_i_1 :> n+  -- inv(i, n) /\ i+1 < n ==> inv(i+1, n)+  impl1 <- impliesPred sym conj0 inv_add_i_1_n+  ule_0_i <- bvUle sym zero i -- trivially true, here for similarity with int test+  ult_i_n <- bvUlt sym i n+  conj1 <- andPred sym ule_0_i ult_i_n+  -- inv(i, n) ==> 0 <= i /\ i < n+  impl2 <- impliesPred sym inv_i_n conj1++  -- inv(i, n) /\ not (i + 1 < n) ==> i + 1 == n+  not_ult_add_i_1_n <- notPred sym ult_add_i_1_n+  conj2 <- andPred sym inv_i_n not_ult_add_i_1_n+  eq_add_i_1_n <- bvEq sym add_i_1 n+  impl3 <- notPred sym =<< impliesPred sym conj2 eq_add_i_1_n++  return ([SomeSymFn inv], [impl0, impl1, impl2], impl3)++synthesis_test ::+  String ->+  (forall sym . IsSymExprBuilder sym => sym -> IO ([SomeSymFn sym], [Pred sym], Pred sym)) ->+  String ->+  (forall sym t fs .+    sym ~ SimpleExprBuilder t fs =>+    sym ->+    LogData ->+    [SomeSymFn sym] ->+    [BoolExpr t] ->+    IO (SatResult (MapF (SymFnWrapper sym) (SymFnWrapper sym)) ())) ->+  (forall t fs a .+    SimpleExprBuilder t fs ->+    LogData ->+    [BoolExpr t] ->+    (SatResult (GroundEvalFn t, Maybe (ExprRangeBindings t)) () -> IO a) ->+    IO a) ->+  TestTree+synthesis_test test_name synthesis_problem solver_name run_solver_synthesis run_solver_in_override =+  testCase (test_name ++ " " ++ solver_name ++ " test") $ withSym FloatIEEERepr $ \sym -> do+    (synth_fns, constraints, goal) <- synthesis_problem sym++    run_solver_in_override sym logData [goal] $ \res -> isSat res @? "sat"++    subst <- run_solver_synthesis sym logData synth_fns constraints >>= \case+      Sat res -> return res+      Unsat{} -> fail "Infeasible"+      Unknown -> fail "Fail"++    goal' <- substituteSymFns sym subst goal+    run_solver_in_override sym logData [goal'] $ \res -> isUnsat res @? "unsat"++main :: IO ()+main = do+  testLevel <- TestLevel . fromMaybe "0" <$> lookupEnv "CI_TEST_LEVEL"+  let solverNames = map SolverName [ "cvc5", "z3" ]+  solvers <- reportSolverVersions testLevel id+    =<< (zip solverNames <$> mapM getSolverVersion solverNames)+  let skipPre4_8_9 why =+        let shouldSkip = case lookup (SolverName "z3") solvers of+              Just (SolverVersion v) -> any (`elem` [ "4.8.8" ]) $ words v+              Nothing -> True+        in if shouldSkip then expectFailBecause why else id+      failureZ3 = "failure with older Z3 versions; upgrade to at least 4.8.9"+  defaultMain $ testGroup "Tests" $+    [ synthesis_test "int" intProblem "cvc5" CVC5.runCVC5SyGuS CVC5.runCVC5InOverride+    , skipPre4_8_9 failureZ3 $ synthesis_test "int" intProblem "z3" Z3.runZ3Horn Z3.runZ3InOverride+    , synthesis_test "bv" bvProblem "cvc5" CVC5.runCVC5SyGuS CVC5.runCVC5InOverride+    ]
test/OnlineSolverTest.hs view
@@ -12,6 +12,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- for TestShow instance  import           Control.Concurrent ( threadDelay )@@ -58,6 +59,8 @@     , AnOnlineSolver @(SMT2.Writer Z3) Proxy, z3Features, z3Options, Just z3Timeout)   , (SolverName "CVC4"     ,  AnOnlineSolver @(SMT2.Writer CVC4) Proxy, cvc4Features, cvc4Options, Just cvc4Timeout)+  , (SolverName "CVC5"+    ,  AnOnlineSolver @(SMT2.Writer CVC5) Proxy, cvc5Features, cvc5Options, Just cvc5Timeout)   , (SolverName "Yices"     , AnOnlineSolver @Yices.Connection Proxy, yicesDefaultFeatures, yicesOptions, Just yicesGoalTimeout)   , (SolverName "Boolector"@@ -339,7 +342,7 @@       -- impactful due to killing the solver process itself).       --       -- This value should also be <= 60% of useableTimeThreshold to-      -- ensure that the solver runs for a siginificantly longer+      -- ensure that the solver runs for a significantly longer       -- period than the test timeout will be set to.       --       -- This value can be adjusted by the developer as needed to@@ -373,6 +376,7 @@       approxTestTimes :: [ (SolverName, Time) ]       approxTestTimes = [ (SolverName "Z3",         2.27 % Second)    -- Z3 4.8.10.  Z3 is good at self timeout.                         , (SolverName "CVC4",       7.5  % Second)    -- CVC4 1.8+                        , (SolverName "CVC5",       0.40  % Second)   -- CVC5 1.0.0                         , (SolverName "Yices",      2.9  % Second)    -- Yices 2.6.1                         , (SolverName "Boolector",  7.2  % Second)    -- Boolector 3.2.1                         , (SolverName "STP",        1.35 % Second)    -- STP 2.3.3@@ -452,7 +456,7 @@                        (longTimeTest sti Nothing)                finish <- getTime Monotonic                let deltaT = (fromInteger $ toNanoSecs $ diffTimeSpec start finish) % nano Second :: Time-               isLeft rslt @? "solver is to fast for valid timeout testing"+               isLeft rslt @? "solver is too fast for valid timeout testing"                assertBool                  ("Solver check query not interruptible (" <>                    show deltaT <> " > expected " <> show useableTimeThreshold <> ")")
+ test/SerializeTestUtils.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE LambdaCase #-}++module SerializeTestUtils where++import           Control.Monad ( when )+import           Control.Monad.IO.Class ( MonadIO, liftIO )+import           Hedgehog+import           System.Directory+import qualified What4.Expr.Builder as S+import qualified What4.Interface as WI+import qualified What4.Serialize.Normalize as WN++import           Prelude+++debugFile :: FilePath+debugFile = "what4serialize.log"++debugReset :: IO ()+debugReset = do e <- doesFileExist debugFile+                when e $ removeFile debugFile++debugOut, alwaysPrint :: MonadIO m => String -> m ()+debugOut msg = liftIO $ do appendFile debugFile (msg <> "\n")+                           -- alwaysPrint  -- comment this out to disable printing+                           return ()+alwaysPrint = liftIO . putStrLn+++showSymFn :: S.ExprSymFn t args ret -> String+showSymFn fn = case S.symFnInfo fn of+  S.DefinedFnInfo _ expr _ -> (show $ WI.printSymExpr expr)+  _ -> ""++symFnEqualityTest :: ( MonadIO m+                     , MonadTest m+                     , sym ~ S.ExprBuilder t st flgs+                     ) =>+                     sym+                  -> WI.SymFn sym args ret+                  -> WI.SymFn sym arts' ret'+                  -> m ()+symFnEqualityTest sym fn1 fn2 = do+  (liftIO $ WN.testEquivSymFn sym fn1 fn2) >>= \case+    WN.ExprEquivalent -> success+    WN.ExprNormEquivalent -> success+    WN.ExprUnequal -> do+      debugOut $ "Resulting functions do not match:\n"+        ++ "fn1:\n" ++ (showSymFn fn1) ++ "\n"+        ++ "fn2:\n" ++ (showSymFn fn2)+      failure
+ test/SerializeTests.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE ImplicitParams #-}+module Main ( main ) where++import qualified Test.Tasty as T+import qualified Control.Exception as CE++import qualified What4.Utils.Serialize as U+import qualified What4.Serialize.Log as U+import           SymFnTests+++allTests :: (U.HasLogCfg) => T.TestTree+allTests = T.testGroup "What4" symFnTests++main :: IO ()+main = do+  logCfg <- U.mkLogCfg "main"+  let ?logCfg = logCfg+  U.withAsyncLinked (U.tmpFileLogEventConsumer (const True) logCfg) $+    const $ T.defaultMain allTests `CE.finally` U.logEndWith logCfg
test/SolverParserTest.hs view
@@ -20,7 +20,7 @@   sugarCube :: CUBE-sugarCube = mkCUBE { inputDir = "test/responses"+sugarCube = mkCUBE { inputDirs = [ "test/responses" ]                    , rootName = "*.rsp"                    , expectedSuffix = ".exp"                    , validParams = [ ("parsing", Just ["strict", "lenient"])
+ test/SymFnTests.hs view
@@ -0,0 +1,242 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE LambdaCase #-}++module SymFnTests where++import           Control.Monad.IO.Class ( MonadIO, liftIO )++import           Data.Parameterized.Classes ( ShowF(..) )+import           Data.Parameterized.Context ( pattern (:>), (!) )+import qualified Data.Parameterized.Context as Ctx+import           Data.Parameterized.Nonce+import           Data.Parameterized.Some+import           Data.Parameterized.TraversableFC+import qualified Data.String as String+import qualified Data.Text as T+import qualified Data.Map as Map+import qualified Data.Map.Ordered as OMap+import           Hedgehog+import qualified LibBF as BF++import           Test.Tasty+import           Test.Tasty.Hedgehog hiding (testProperty)+import           SerializeTestUtils+import qualified What4.Expr.Builder as S+import           What4.BaseTypes+import qualified What4.Interface as WI++import qualified What4.Serialize.Printer as WOUT+import qualified What4.Serialize.Parser as WIN+import qualified What4.Serialize.FastSExpr as WSF+++import           Prelude+++symFnTests :: [TestTree]+symFnTests = [+  testGroup "SymFns" (mconcat [+    testBasicArguments WIN.parseSExpr+    , testFunctionCalls WIN.parseSExpr+    , testExpressions WIN.parseSExpr+    , testBasicArguments WSF.parseSExpr+    , testFunctionCalls WSF.parseSExpr+    , testExpressions WSF.parseSExpr+    ])+  ]++data BuilderData t = NoBuilderData++floatSinglePrecision :: FloatPrecisionRepr Prec32+floatSinglePrecision = knownRepr++floatSingleType :: BaseTypeRepr (BaseFloatType Prec32)+floatSingleType = BaseFloatRepr floatSinglePrecision++testBasicArguments :: (T.Text -> Either String WIN.SExpr) -> [TestTree]+testBasicArguments parseSExpr =+    [ testProperty "same argument type" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseIntegerRepr :> BaseIntegerRepr) $ \sym bvs -> do+          let i1 = bvs ! Ctx.i1of2+          let i2 = bvs ! Ctx.i2of2+          WI.intAdd sym i1 i2+    , testProperty "different argument types" $+         withTests 1 $+         property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseIntegerRepr :> BaseBoolRepr) $ \sym bvs -> do+          let i1 = bvs ! Ctx.i1of2+          let b1 = bvs ! Ctx.i2of2+          WI.baseTypeIte sym b1 i1 i1+    ]+++testFunctionCalls :: (T.Text -> Either String WIN.SExpr) -> [TestTree]+testFunctionCalls parseSExpr =+    [ testProperty "no arguments" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr Ctx.empty $ \sym _ -> do+          ufn <- WI.freshTotalUninterpFn sym (WI.safeSymbol "ufn") Ctx.empty BaseBoolRepr+          WI.applySymFn sym ufn Ctx.empty+    , testProperty "two inner arguments" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr Ctx.empty $ \sym _ -> do+          i1 <- WI.intLit sym 0+          let b1 = WI.truePred sym+          ufn <- WI.freshTotalUninterpFn sym (WI.safeSymbol "ufn") (Ctx.empty :> BaseIntegerRepr :> BaseBoolRepr) BaseBoolRepr+          WI.applySymFn sym ufn (Ctx.empty :> i1 :> b1)+    , testProperty "argument passthrough" $+         withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseBoolRepr :> BaseIntegerRepr) $ \sym bvs -> do+          let i1 = bvs ! Ctx.i2of2+          let b1 = bvs ! Ctx.i1of2+          ufn <- WI.freshTotalUninterpFn sym (WI.safeSymbol "ufn") (Ctx.empty :> BaseIntegerRepr :> BaseBoolRepr) BaseBoolRepr+          WI.applySymFn sym ufn (Ctx.empty :> i1 :> b1)+    ]+++testExpressions :: (T.Text -> Either String WIN.SExpr) -> [TestTree]+testExpressions parseSExpr =+    [ testProperty "negative ints" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr Ctx.empty $ \sym _ -> do+          WI.intLit sym (-1)+    , testProperty "float lit" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr Ctx.empty $ \sym _ -> do+          WI.floatLit sym floatSinglePrecision (BF.bfFromInt 100)+    , testProperty "simple struct" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr Ctx.empty $ \sym _ -> do+          i1 <- WI.intLit sym 0+          let b1 = WI.truePred sym+          WI.mkStruct sym (Ctx.empty :> i1 :> b1)+    , testProperty "struct field access" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseStructRepr (Ctx.empty :> BaseIntegerRepr :> BaseBoolRepr)) $ \sym bvs -> do+          let struct = bvs ! Ctx.baseIndex+          i1 <- WI.structField sym struct Ctx.i1of2+          b1 <- WI.structField sym struct Ctx.i2of2+          WI.mkStruct sym (Ctx.empty :> b1 :> i1)+    --, testProperty "simple constant array" $+    --    property $ mkEquivalenceTest Ctx.empty $ \sym _ -> do+    --      i1 <- WI.intLit sym 1+    --      WI.constantArray sym (Ctx.empty :> BaseIntegerRepr) i1+    , testProperty "array update" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseArrayRepr (Ctx.empty :> BaseIntegerRepr) BaseIntegerRepr) $ \sym bvs -> do+          i1 <- WI.intLit sym 1+          i2 <- WI.intLit sym 2+          let arr = bvs ! Ctx.baseIndex+          WI.arrayUpdate sym arr (Ctx.empty :> i1) i2+    , testProperty "integer to bitvector" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> BaseIntegerRepr) $ \sym bvs -> do+          let i1 = bvs ! Ctx.baseIndex+          WI.integerToBV sym i1 (WI.knownNat @32)+    , testProperty "float negate" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> floatSingleType ) $ \sym flts -> do+          let f1 = flts ! Ctx.baseIndex+          WI.floatNeg sym f1+    , testProperty "float abs" $+        withTests 1 $+        property $ mkEquivalenceTest parseSExpr (Ctx.empty :> floatSingleType ) $ \sym flts -> do+          let f1 = flts ! Ctx.baseIndex+          WI.floatAbs sym f1+    ]++mkEquivalenceTest :: forall m args ret+                   . ( MonadTest m+                     , MonadIO m+                     )+                  => (T.Text -> Either String WIN.SExpr)+                  -> Ctx.Assignment BaseTypeRepr args+                  -> (forall sym+                       . WI.IsSymExprBuilder sym+                      => sym+                      -> Ctx.Assignment (WI.SymExpr sym) args+                      -> IO (WI.SymExpr sym ret))+                  -> m ()+mkEquivalenceTest parseSExpr argTs getExpr = do+  Some r <- liftIO $ newIONonceGenerator+  sym <- liftIO $ S.newExprBuilder S.FloatRealRepr NoBuilderData r+  liftIO $ S.startCaching sym+  bvs <- liftIO $ forFC argTs $ \repr -> do+    n <- freshNonce r+    let nm = "bv" ++ show (indexValue n)+    WI.freshBoundVar sym (WI.safeSymbol nm) repr+  e <- liftIO $ getExpr sym (fmapFC (WI.varExpr sym) bvs)+  go sym bvs e+  where+    go :: forall sym t flgs st .+          ( WI.IsSymExprBuilder sym+          , sym ~ S.ExprBuilder t st flgs+          , ShowF (WI.SymExpr sym)+          )+       => sym+       -> Ctx.Assignment (WI.BoundVar sym) args+       -> WI.SymExpr sym ret+       -> m ()+    go sym bvs expr = do+      fn1 <- liftIO $ WI.definedFn sym (WI.safeSymbol "fn") bvs expr WI.NeverUnfold+      let scfg = WOUT.Config { WOUT.cfgAllowFreeVars = True+                             , WOUT.cfgAllowFreeSymFns = True+                             }+          res = WOUT.serializeSymFnWithConfig scfg fn1+          fnText = WOUT.printSExpr mempty $ WOUT.resSExpr res+          fnMap = Map.fromList $ map (\(x,y)->(y,x)) $ OMap.assocs $ WOUT.resSymFnEnv res+          exprMap = Map.fromList $+                    map (\((Some bv),freshName) ->+                           (freshName, (Some (WI.varExpr sym bv))))+                    $ OMap.assocs+                    $ WOUT.resFreeVarEnv res+      -- lcfg <- liftIO $ Log.mkLogCfg "rndtrip"+      deser <- do+        dcfg <- return $ (WIN.defaultConfig sym)+                { WIN.cSymFnLookup = \nm ->+                    case Map.lookup nm fnMap of+                      Nothing -> return Nothing+                      Just (WOUT.SomeExprSymFn fn) -> return $ Just (WIN.SomeSymFn fn)+                , WIN.cExprLookup = \nm ->+                    case Map.lookup nm exprMap of+                      Nothing -> return Nothing+                      Just (Some x) -> return $ Just (Some x)+                }+        case parseSExpr fnText of+          Left errMsg -> return $ Left errMsg+          Right sexpr -> liftIO $ WIN.deserializeSymFnWithConfig sym dcfg sexpr+      case deser of+        Left err -> do+          debugOut $ "Unexpected deserialization error: " ++ err ++ "!\n S-expression:\n"+          debugOut $ (T.unpack fnText) ++ "\n"+          failure+        Right (WIN.SomeSymFn fn2) -> do+          fn1out <- liftIO $ WI.definedFn sym (WI.safeSymbol "fn") bvs expr WI.NeverUnfold+          symFnEqualityTest sym fn1out fn2++-- | Create a 'T.TestTree' from a Hedgehog 'Property'.+--+-- Note that @tasty-hedgehog@'s version of 'testProperty' has been deprecated+-- in favor of 'testPropertyNamed', whose second argument is intended to+-- represent the name of a top-level 'Property' value to run in the event that+-- the test fails. See https://github.com/qfpl/tasty-hedgehog/pull/42.+--+-- That being said, @what4-serialize@ currently does not define any of the+-- properties that it tests as top-level values. In the+-- meantime, we avoid incurring deprecation warnings by defining our own+-- version of 'testProperty'. The downside to this workaround is that if a+-- property fails, the error message it will produce will likely suggest+-- running ill-formed Haskell code, so users will have to use context clues to+-- determine how to /actually/ reproduce the error.+testProperty :: TestName -> Property -> TestTree+testProperty name = testPropertyNamed name (String.fromString name)
test/TestTemplate.hs view
@@ -11,7 +11,7 @@ module Main where  import Control.Exception-import Control.Monad ((<=<)) -- , when)+import Control.Monad ((<=<), unless) import Control.Monad.IO.Class (liftIO) import           Control.Monad.Trans.Maybe import Data.Bits@@ -22,6 +22,7 @@ import Data.Parameterized.Some import Data.String import Numeric (showHex)+import System.Exit (exitFailure) -- import System.IO  import LibBF@@ -78,8 +79,8 @@                             pure (fromString (show t), p)                        | Some t <- xs                        ]-     _ <- checkSequential $ Group "Float tests" tests-     return ()+     testsPassed <- checkSequential $ Group "Float tests" tests+     unless testsPassed exitFailure   data FUnOp
what4.cabal view
@@ -1,9 +1,9 @@ Cabal-version: 2.4 Name:          what4-Version:       1.3+Version:       1.4 Author:        Galois Inc.-Maintainer:    jhendrix@galois.com, rdockins@galois.com-Copyright:     (c) Galois, Inc 2014-2021+Maintainer:    rscott@galois.com, kquick@galois.com+Copyright:     (c) Galois, Inc 2014-2023 License:       BSD-3-Clause License-file:  LICENSE Build-type:    Simple@@ -16,7 +16,7 @@   What4 is a generic library for representing values as symbolic formulae which may   contain references to symbolic values, representing unknown variables.   It provides support for communicating with a variety of SAT and SMT solvers,-  including Z3, CVC4, Yices, Boolector, STP, and dReal.+  including Z3, CVC4, CVC5, Yices, Boolector, STP, and dReal.    The data representation types make heavy use of GADT-style type indices   to ensure type-correct manipulation of symbolic values.@@ -91,18 +91,18 @@ library   import: bldflags   build-depends:-    base >= 4.8 && < 5,+    base >= 4.10 && < 5,     async,     attoparsec >= 0.13,     bimap >= 0.2,     bifunctors >= 5,+    BoundedChan >= 1 && < 2,     bv-sized >= 1.0.0,     bytestring >= 0.10,     deriving-compat >= 0.5,     concurrent-extra >= 0.7 && < 0.8,     config-value >= 0.8 && < 0.9,     containers >= 0.5.0.0,-    data-binary-ieee754,     deepseq >= 1.3,     directory >= 1.2.2,     exceptions >= 0.10,@@ -113,19 +113,26 @@     io-streams >= 1.5,     lens >= 4.18,     libBF >= 0.6 && < 0.7,+    megaparsec >= 8 && < 10,     mtl >= 2.2.1,+    ordered-containers >= 0.2 && < 0.3,     panic >= 0.3,     parameterized-utils >= 2.1 && < 2.2,+    parsec >= 3 && < 4,     prettyprinter >= 1.7.0,     process >= 1.2,+    s-cargot >= 0.1 && < 0.2,     scientific >= 0.3.6,+    stm,     temporary >= 1.2,     template-haskell,-    text >= 1.2.4.0 && < 1.3,+    text >= 1.2.4.0 && < 2.1,     th-abstraction >=0.1 && <0.5,     th-lift >= 0.8.2 && < 0.9,     th-lift-instances >= 0.1 && < 0.2,+    time >= 1.8 && < 1.13,     transformers >= 0.4,+    unliftio >= 0.2 && < 0.3,     unordered-containers >= 0.2.10,     utf8-string >= 1.0.1,     vector >= 0.12.1,@@ -175,10 +182,18 @@     What4.Expr.WeightedSum     What4.Expr.UnaryBV +    What4.Serialize.FastSExpr+    What4.Serialize.Log+    What4.Serialize.Normalize+    What4.Serialize.Parser+    What4.Serialize.Printer+    What4.Serialize.SETokens+     What4.Solver     What4.Solver.Adapter     What4.Solver.Boolector     What4.Solver.CVC4+    What4.Solver.CVC5     What4.Solver.DReal     What4.Solver.ExternalABC     What4.Solver.STP@@ -217,6 +232,7 @@     What4.Utils.OnlyIntRepr     What4.Utils.Process     What4.Utils.ResolveBounds.BV+    What4.Utils.Serialize     What4.Utils.Streams     What4.Utils.StringLiteral     What4.Utils.Word16String@@ -236,6 +252,19 @@     parameterized-utils,     what4 +test-suite abduct+  import: testdefs-hunit+  type: exitcode-stdio-1.0+  main-is: Abduct.hs+  default-language: Haskell2010++  build-depends:+    base,+    parameterized-utils,+    what4,+    text,+    temporary+ test-suite adapter-test   import: bldflags, testdefs-hunit   type: exitcode-stdio-1.0@@ -257,7 +286,6 @@     bv-sized,     bytestring,     containers,-    data-binary-ieee754,     lens,     mtl >= 2.2.1,     process,@@ -296,7 +324,6 @@     bytestring,     clock,     containers,-    data-binary-ieee754,     exceptions,     lens,     prettyprinter,@@ -320,7 +347,6 @@     bv-sized,     bytestring,     containers,-    data-binary-ieee754,     libBF,     prettyprinter,     process,@@ -396,5 +422,42 @@                , exceptions                , io-streams                , lumberjack-               , tasty-sugar >= 1.1 && < 1.2+               , tasty-sugar >= 2.0 && < 2.1                , text++test-suite what4-serialize-tests+  default-language: Haskell2010+  type: exitcode-stdio-1.0+  ghc-options: -Wall -Wcompat -rtsopts -threaded+  hs-source-dirs: test+  main-is: SerializeTests.hs+  other-modules: SymFnTests, SerializeTestUtils+  build-depends:   what4+                 , base+                 , containers+                 , directory+                 , exceptions+                 , hedgehog+                 , libBF+                 , tasty+                 , tasty-hunit+                 , tasty-hedgehog+                 , text+                 , parameterized-utils+                 , async+                 , directory+                 , ordered-containers++test-suite invariant-synthesis+  import: bldflags, testdefs-hunit+  type: exitcode-stdio-1.0++  main-is: InvariantSynthesis.hs++  other-modules: ProbeSolvers++  build-depends:+    bv-sized,+    process,+    tasty-expected-failure >= 0.12 && < 0.13+