{-# LANGUAGE UndecidableInstances #-} {-| Module : What4.Solver.Partial Description : Representation of partial values Copyright : (c) Galois, Inc 2014-2020 License : BSD3 Maintainer : Langston Barrett Often, various operations on values are only partially defined (in the case of Crucible expressions, consider loading a value from a pointer - this is only defined in the case that the pointer is valid and non-null). The 'PartExpr' type allows for packaging values together with predicates that express their partiality: the value is only valid if the predicate is true. -} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module What4.Partial ( -- ** Partial Partial(..) , partialPred , partialValue -- ** PartialWithErr , PartialWithErr(..) -- ** PartExpr , PartExpr , pattern PE , pattern Unassigned , mkPE , justPartExpr , maybePartExpr , joinMaybePE -- ** PartialT , PartialT(..) , runPartialT , returnUnassigned , returnMaybe , returnPartial , addCondition , mergePartial , mergePartials ) where #if !MIN_VERSION_base(4,13,0) import Control.Monad.Fail( MonadFail ) import qualified Control.Monad.Fail #endif import GHC.Generics (Generic, Generic1) import Data.Data (Data) import Control.Monad.IO.Class import Control.Monad.Trans.Class import What4.BaseTypes import What4.Interface (IsExprBuilder, SymExpr, IsExpr, Pred) import What4.Interface (truePred, andPred, notPred, itePred, asConstantPred) import Control.Lens.TH (makeLenses) import Data.Bifunctor.TH (deriveBifunctor, deriveBifoldable, deriveBitraversable) import Data.Eq.Deriving (deriveEq1, deriveEq2) import Data.Ord.Deriving (deriveOrd1, deriveOrd2) import Text.Show.Deriving (deriveShow1, deriveShow2) ------------------------------------------------------------------------ -- ** Partial -- | A partial value represents a value that may or may not be valid. -- -- The '_partialPred' field represents a predicate (optionally with additional -- provenance information) embodying the value's partiality. data Partial p v = Partial { _partialPred :: !p , _partialValue :: !v } deriving (Data, Eq, Functor, Generic, Generic1, Foldable, Traversable, Ord, Show) makeLenses ''Partial $(deriveBifunctor ''Partial) $(deriveBifoldable ''Partial) $(deriveBitraversable ''Partial) $(deriveEq1 ''Partial) $(deriveEq2 ''Partial) $(deriveOrd1 ''Partial) $(deriveOrd2 ''Partial) $(deriveShow1 ''Partial) $(deriveShow2 ''Partial) -- | Create a 'Partial' expression from a value that is always defined. total :: IsExprBuilder sym => sym -> v -> Partial (Pred sym) v total sym = Partial (truePred sym) ------------------------------------------------------------------------ -- ** PartialWithErr -- | Either a partial value, or a straight-up error. data PartialWithErr e p v = NoErr (Partial p v) | Err e deriving (Data, Eq, Functor, Generic, Generic1, Foldable, Traversable, Ord, Show) $(deriveBifunctor ''PartialWithErr) $(deriveBifoldable ''PartialWithErr) $(deriveBitraversable ''PartialWithErr) $(deriveEq1 ''PartialWithErr) $(deriveEq2 ''PartialWithErr) $(deriveOrd1 ''PartialWithErr) $(deriveOrd2 ''PartialWithErr) $(deriveShow1 ''PartialWithErr) $(deriveShow2 ''PartialWithErr) ------------------------------------------------------------------------ -- ** PartExpr -- | A 'PartExpr' is a 'PartialWithErr' that provides no information about what -- went wrong. Its name is historic. type PartExpr p v = PartialWithErr () p v pattern Unassigned :: PartExpr p v pattern Unassigned = Err () pattern PE :: p -> v -> PartExpr p v pattern PE p v = NoErr (Partial p v) -- Claim that the above two patterns are exhaustive for @PartExpr p v@ {-# COMPLETE Unassigned, PE #-} mkPE :: IsExpr p => p BaseBoolType -> a -> PartExpr (p BaseBoolType) a mkPE p v = case asConstantPred p of Just False -> Unassigned _ -> PE p v -- | Create a part expression from a value that is always defined. justPartExpr :: IsExprBuilder sym => sym -> v -> PartExpr (Pred sym) v justPartExpr sym = NoErr . total sym -- | Create a part expression from a maybe value. maybePartExpr :: IsExprBuilder sym => sym -> Maybe a -> PartExpr (Pred sym) a maybePartExpr _ Nothing = Unassigned maybePartExpr sym (Just r) = justPartExpr sym r -- | @'joinMaybePE' = 'Data.Maybe.fromMaybe' 'Unassigned'@. joinMaybePE :: Maybe (PartExpr p v) -> PartExpr p v joinMaybePE Nothing = Unassigned joinMaybePE (Just pe) = pe ------------------------------------------------------------------------ -- *** Merge -- | If-then-else on partial expressions. mergePartial :: (IsExprBuilder sym, MonadIO m) => sym -> (Pred sym -> a -> a -> PartialT sym m a) {- ^ Operation to combine inner values. The 'Pred' parameter is the if-then-else condition. -} -> Pred sym {- ^ condition to merge on -} -> PartExpr (Pred sym) a {- ^ 'if' value -} -> PartExpr (Pred sym) a {- ^ 'then' value -} -> m (PartExpr (Pred sym) a) {-# SPECIALIZE mergePartial :: IsExprBuilder sym => sym -> (Pred sym -> a -> a -> PartialT sym IO a) -> Pred sym -> PartExpr (Pred sym) a -> PartExpr (Pred sym) a -> IO (PartExpr (Pred sym) a) #-} mergePartial _ _ _ Unassigned Unassigned = return Unassigned mergePartial sym _ c (PE px x) Unassigned = do p <- liftIO $ andPred sym px c return $! mkPE p x mergePartial sym _ c Unassigned (PE py y) = do p <- liftIO (andPred sym py =<< notPred sym c) return $! mkPE p y mergePartial sym f c (PE px x) (PE py y) = do p <- liftIO (itePred sym c px py) runPartialT sym p (f c x y) -- | Merge a collection of partial values in an if-then-else tree. -- For example, if we merge a list like @[(xp,x),(yp,y),(zp,z)]@, -- we get a value that is morally equivalent to: -- @if xp then x else (if yp then y else (if zp then z else undefined))@. mergePartials :: (IsExprBuilder sym, MonadIO m) => sym -> (Pred sym -> a -> a -> PartialT sym m a) {- ^ Operation to combine inner values. The 'Pred' parameter is the if-then-else condition. -} -> [(Pred sym, PartExpr (Pred sym) a)] {- ^ values to merge -} -> m (PartExpr (Pred sym) a) mergePartials sym f = go where go [] = return Unassigned go ((c,x):xs) = do y <- go xs mergePartial sym f c x y ------------------------------------------------------------------------ -- *** PartialT -- | A monad transformer which enables symbolic partial computations to run by -- maintaining a predicate on the value. newtype PartialT sym m a = PartialT { unPartial :: sym -> Pred sym -> m (PartExpr (Pred sym) a) } -- | Run a partial computation. runPartialT :: sym -- ^ Solver interface -> Pred sym -- ^ Initial condition -> PartialT sym m a -- ^ Computation to run. -> m (PartExpr (Pred sym) a) runPartialT sym p f = unPartial f sym p instance Functor m => Functor (PartialT sym m) where fmap f mx = PartialT $ \sym p -> fmap resolve (unPartial mx sym p) where resolve Unassigned = Unassigned resolve (PE q x) = PE q (f x) -- We depend on the monad transformer as partialT explicitly orders -- the calls to the functions in (<*>). This ordering allows us to -- avoid having any requirements that sym implement a partial interface. instance (IsExpr (SymExpr sym), Monad m) => Applicative (PartialT sym m) where pure a = PartialT $ \_ p -> pure $! mkPE p a mf <*> mx = mf >>= \f -> mx >>= \x -> pure (f x) instance (IsExpr (SymExpr sym), Monad m) => Monad (PartialT sym m) where return = pure m >>= h = PartialT $ \sym p -> do pr <- unPartial m sym p case pr of Unassigned -> pure Unassigned PE q r -> unPartial (h r) sym q #if !MIN_VERSION_base(4,13,0) fail msg = PartialT $ \_ _ -> fail msg #endif instance (IsExpr (SymExpr sym), MonadFail m) => MonadFail (PartialT sym m) where fail msg = PartialT $ \_ _ -> fail msg instance MonadTrans (PartialT sym) where lift m = PartialT $ \_ p -> PE p <$> m instance (IsExpr (SymExpr sym), MonadIO m) => MonadIO (PartialT sym m) where liftIO = lift . liftIO -- | End the partial computation and just return the unassigned value. returnUnassigned :: Applicative m => PartialT sym m a returnUnassigned = PartialT $ \_ _ -> pure Unassigned -- | Lift a 'Maybe' value to a partial expression. returnMaybe :: (IsExpr (SymExpr sym), Applicative m) => Maybe a -> PartialT sym m a returnMaybe Nothing = returnUnassigned returnMaybe (Just a) = PartialT $ \_ p -> pure (mkPE p a) -- | Return a partial expression. -- -- This joins the partial expression with the current constraints on the -- current computation. returnPartial :: (IsExprBuilder sym, MonadIO m) => PartExpr (Pred sym) a -> PartialT sym m a returnPartial Unassigned = returnUnassigned returnPartial (PE q a) = PartialT $ \sym p -> liftIO (mkPE <$> andPred sym p q <*> pure a) -- | Add an extra condition to the current partial computation. addCondition :: (IsExprBuilder sym, MonadIO m) => Pred sym -> PartialT sym m () addCondition q = returnPartial (mkPE q ())