{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module What4.Utils.ResolveBounds.BV
( resolveSymBV
, SearchStrategy(..)
, ResolvedSymBV(..)
) where
import Data.BitVector.Sized ( BV )
import qualified Data.BitVector.Sized as BV
import qualified Data.Parameterized.NatRepr as PN
import qualified Prettyprinter as PP
import qualified What4.Expr.Builder as WEB
import qualified What4.Expr.GroundEval as WEG
import qualified What4.Interface as WI
import qualified What4.Protocol.Online as WPO
import qualified What4.Protocol.SMTWriter as WPS
import qualified What4.SatResult as WSat
import qualified What4.Utils.BVDomain.Arith as WUBA
data ResolvedSymBV w
= BVConcrete (BV w)
| BVSymbolic (WUBA.Domain w)
instance Show (ResolvedSymBV w) where
showsPrec :: Int -> ResolvedSymBV w -> ShowS
showsPrec Int
_p ResolvedSymBV w
res =
case ResolvedSymBV w
res of
BVConcrete BV w
bv ->
String -> ShowS
showString String
"BVConcrete " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> BV w -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 BV w
bv
BVSymbolic Domain w
d ->
let (Integer
lb, Integer
ub) = Domain w -> (Integer, Integer)
forall (w :: Nat). Domain w -> (Integer, Integer)
WUBA.ubounds Domain w
d in
String -> ShowS
showString String
"BVSymbolic ["
ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Integer -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 Integer
lb
ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ShowS
showString String
", "
ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Integer -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 Integer
ub
ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ShowS
showString String
"]"
data SearchStrategy
= ExponentialSearch
| BinarySearch
instance PP.Pretty SearchStrategy where
pretty :: forall ann. SearchStrategy -> Doc ann
pretty SearchStrategy
ExponentialSearch = String -> Doc ann
forall ann. String -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty String
"exponential search"
pretty SearchStrategy
BinarySearch = String -> Doc ann
forall ann. String -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty String
"binary search"
resolveSymBV ::
forall w sym solver scope st fs
. ( 1 PN.<= w
, sym ~ WEB.ExprBuilder scope st fs
, WPO.OnlineSolver solver
)
=> sym
-> SearchStrategy
-> PN.NatRepr w
-> WPO.SolverProcess scope solver
-> WI.SymBV sym w
-> IO (ResolvedSymBV w)
resolveSymBV :: forall (w :: Nat) sym solver scope (st :: Type -> Type) fs.
(1 <= w, sym ~ ExprBuilder scope st fs, OnlineSolver solver) =>
sym
-> SearchStrategy
-> NatRepr w
-> SolverProcess scope solver
-> SymBV sym w
-> IO (ResolvedSymBV w)
resolveSymBV sym
sym SearchStrategy
searchStrat NatRepr w
w SolverProcess scope solver
proc SymBV sym w
symBV =
case Expr scope (BaseBVType w) -> Maybe (BV w)
forall (w :: Nat). Expr scope (BaseBVType w) -> Maybe (BV w)
forall (e :: BaseType -> Type) (w :: Nat).
IsExpr e =>
e (BaseBVType w) -> Maybe (BV w)
WI.asBV SymBV sym w
Expr scope (BaseBVType w)
symBV of
Just BV w
bv -> ResolvedSymBV w -> IO (ResolvedSymBV w)
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure (ResolvedSymBV w -> IO (ResolvedSymBV w))
-> ResolvedSymBV w -> IO (ResolvedSymBV w)
forall a b. (a -> b) -> a -> b
$ BV w -> ResolvedSymBV w
forall (w :: Nat). BV w -> ResolvedSymBV w
BVConcrete BV w
bv
Maybe (BV w)
Nothing -> do
modelForBV <- SolverProcess scope solver -> IO (BV w) -> IO (BV w)
forall (m :: Type -> Type) solver scope a.
(MonadIO m, MonadMask m, SMTReadWriter solver) =>
SolverProcess scope solver -> m a -> m a
WPO.inNewFrame SolverProcess scope solver
proc (IO (BV w) -> IO (BV w)) -> IO (BV w) -> IO (BV w)
forall a b. (a -> b) -> a -> b
$ do
msat <- SolverProcess scope solver
-> String -> IO (SatResult (GroundEvalFn scope) ())
forall solver scope.
SMTReadWriter solver =>
SolverProcess scope solver
-> String -> IO (SatResult (GroundEvalFn scope) ())
WPO.checkAndGetModel SolverProcess scope solver
proc String
"resolveSymBV (check with initial assumptions)"
model <- case msat of
SatResult (GroundEvalFn scope) ()
WSat.Unknown -> IO (GroundEvalFn scope)
forall a. IO a
failUnknown
WSat.Unsat{} -> String -> IO (GroundEvalFn scope)
forall a. String -> IO a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
"resolveSymBV: Initial assumptions are unsatisfiable"
WSat.Sat GroundEvalFn scope
model -> GroundEvalFn scope -> IO (GroundEvalFn scope)
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure GroundEvalFn scope
model
WEG.groundEval model symBV
isSymbolic <- WPO.inNewFrame proc $ do
block <- WI.notPred sym =<< WI.bvEq sym symBV =<< WI.bvLit sym w modelForBV
WPS.assume conn block
msat <- WPO.check proc "resolveSymBV (check under assumption that model cannot happen)"
case msat of
SatResult () ()
WSat.Unknown -> IO Bool
forall a. IO a
failUnknown
WSat.Sat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
True
WSat.Unsat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
False
if isSymbolic
then
case searchStrat of
SearchStrategy
ExponentialSearch -> do
lowerBound <- BV w -> IO (BV w)
computeLowerBoundExponential BV w
modelForBV
upperBound <- computeUpperBoundExponential modelForBV
pure $ BVSymbolic $ WUBA.range w
(BV.asUnsigned lowerBound) (BV.asUnsigned upperBound)
SearchStrategy
BinarySearch -> do
lowerBound <- BV w -> BV w -> IO (BV w)
computeLowerBoundBinary BV w
bvZero BV w
bvMaxUnsigned
upperBound <- computeUpperBoundBinary bvZero bvMaxUnsigned
pure $ BVSymbolic $ WUBA.range w
(BV.asUnsigned lowerBound) (BV.asUnsigned upperBound)
else pure $ BVConcrete modelForBV
where
conn :: WPS.WriterConn scope solver
conn :: WriterConn scope solver
conn = SolverProcess scope solver -> WriterConn scope solver
forall scope solver.
SolverProcess scope solver -> WriterConn scope solver
WPO.solverConn SolverProcess scope solver
proc
failUnknown :: forall a. IO a
failUnknown :: forall a. IO a
failUnknown = String -> IO a
forall a. String -> IO a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
"resolveSymBV: Resolving value yielded UNKNOWN"
bvZero :: BV w
bvZero :: BV w
bvZero = NatRepr w -> BV w
forall (w :: Nat). NatRepr w -> BV w
BV.zero NatRepr w
w
bvOne :: BV w
bvOne :: BV w
bvOne = NatRepr w -> BV w
forall (w :: Nat). (1 <= w) => NatRepr w -> BV w
BV.one NatRepr w
w
bvTwo :: BV w
bvTwo :: BV w
bvTwo = NatRepr w -> Integer -> BV w
forall (w :: Nat). NatRepr w -> Integer -> BV w
BV.mkBV NatRepr w
w Integer
2
bvMaxUnsigned :: BV w
bvMaxUnsigned :: BV w
bvMaxUnsigned = NatRepr w -> BV w
forall (w :: Nat). NatRepr w -> BV w
BV.maxUnsigned NatRepr w
w
computeLowerBoundExponential :: BV w -> IO (BV w)
computeLowerBoundExponential :: BV w -> IO (BV w)
computeLowerBoundExponential BV w
start = BV w -> BV w -> IO (BV w)
go BV w
start BV w
bvOne
where
go :: BV w -> BV w -> IO (BV w)
go :: BV w -> BV w -> IO (BV w)
go BV w
previouslyTried BV w
diff
|
BV w
start BV w -> BV w -> Bool
forall (w :: Nat). BV w -> BV w -> Bool
`BV.ult` BV w
diff
= BV w -> BV w -> IO (BV w)
computeLowerBoundBinary BV w
bvZero BV w
previouslyTried
|
Bool
otherwise
= do let nextToTry :: BV w
nextToTry = NatRepr w -> BV w -> BV w -> BV w
forall (w :: Nat). NatRepr w -> BV w -> BV w -> BV w
BV.sub NatRepr w
w BV w
start BV w
diff
exceedsLB <- BV w -> IO Bool
checkExceedsLowerBound BV w
nextToTry
if |
exceedsLB
-> computeLowerBoundBinary nextToTry previouslyTried
|
BV.asUnsigned diff * 2 > BV.asUnsigned bvMaxUnsigned
-> computeLowerBoundBinary bvZero nextToTry
|
otherwise
-> go nextToTry $ BV.mul w diff bvTwo
computeLowerBoundBinary :: BV w -> BV w -> IO (BV w)
computeLowerBoundBinary :: BV w -> BV w -> IO (BV w)
computeLowerBoundBinary BV w
l BV w
r
|
BV w
l BV w -> BV w -> Bool
forall a. Eq a => a -> a -> Bool
== BV w
r
= BV w -> IO (BV w)
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure BV w
l
|
NatRepr w -> BV w -> BV w -> BV w
forall (w :: Nat). NatRepr w -> BV w -> BV w -> BV w
BV.sub NatRepr w
w BV w
r BV w
l BV w -> BV w -> Bool
forall a. Ord a => a -> a -> Bool
< BV w
bvTwo
= do lExceedsLB <- BV w -> IO Bool
checkExceedsLowerBound BV w
l
pure $ if lExceedsLB then r else l
|
Bool
otherwise
= do let nextToTry :: BV w
nextToTry = NatRepr w -> Integer -> BV w
forall (w :: Nat). NatRepr w -> Integer -> BV w
BV.mkBV NatRepr w
w ((BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
l Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
r) Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
2)
exceedsLB <- BV w -> IO Bool
checkExceedsLowerBound BV w
nextToTry
if exceedsLB
then computeLowerBoundBinary nextToTry r
else computeLowerBoundBinary l nextToTry
checkExceedsLowerBound :: BV w -> IO Bool
checkExceedsLowerBound :: BV w -> IO Bool
checkExceedsLowerBound BV w
bv = SolverProcess scope solver -> IO Bool -> IO Bool
forall (m :: Type -> Type) solver scope a.
(MonadIO m, MonadMask m, SMTReadWriter solver) =>
SolverProcess scope solver -> m a -> m a
WPO.inNewFrame SolverProcess scope solver
proc (IO Bool -> IO Bool) -> IO Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ do
leLowerBound <- sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
forall (w :: Nat).
(1 <= w) =>
sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
forall sym (w :: Nat).
(IsExprBuilder sym, 1 <= w) =>
sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
WI.bvUle sym
sym SymBV sym w
symBV (Expr scope (BaseBVType w) -> IO (BoolExpr scope))
-> IO (Expr scope (BaseBVType w)) -> IO (BoolExpr scope)
forall (m :: Type -> Type) a b. Monad m => (a -> m b) -> m a -> m b
=<< sym -> NatRepr w -> BV w -> IO (SymBV sym w)
forall (w :: Nat).
(1 <= w) =>
sym -> NatRepr w -> BV w -> IO (SymBV sym w)
forall sym (w :: Nat).
(IsExprBuilder sym, 1 <= w) =>
sym -> NatRepr w -> BV w -> IO (SymBV sym w)
WI.bvLit sym
sym NatRepr w
w BV w
bv
WPS.assume conn leLowerBound
msat <- WPO.check proc "resolveSymBV (check if lower bound has been exceeded)"
case msat of
SatResult () ()
WSat.Unknown -> IO Bool
forall a. IO a
failUnknown
WSat.Sat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
False
WSat.Unsat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
True
computeUpperBoundExponential :: BV w -> IO (BV w)
computeUpperBoundExponential :: BV w -> IO (BV w)
computeUpperBoundExponential BV w
start = BV w -> BV w -> IO (BV w)
go BV w
start BV w
bvOne
where
go :: BV w -> BV w -> IO (BV w)
go :: BV w -> BV w -> IO (BV w)
go BV w
previouslyTried BV w
diff
|
BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
start Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
diff Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
bvMaxUnsigned
= BV w -> BV w -> IO (BV w)
computeUpperBoundBinary BV w
previouslyTried BV w
bvMaxUnsigned
| Bool
otherwise
= do let nextToTry :: BV w
nextToTry = NatRepr w -> BV w -> BV w -> BV w
forall (w :: Nat). NatRepr w -> BV w -> BV w -> BV w
BV.add NatRepr w
w BV w
start BV w
diff
exceedsUB <- BV w -> IO Bool
checkExceedsUpperBound BV w
nextToTry
if |
exceedsUB
-> computeUpperBoundBinary previouslyTried nextToTry
|
BV.asUnsigned diff * 2 > BV.asUnsigned bvMaxUnsigned
-> computeUpperBoundBinary nextToTry bvMaxUnsigned
|
otherwise
-> go nextToTry $ BV.mul w diff bvTwo
computeUpperBoundBinary :: BV w -> BV w -> IO (BV w)
computeUpperBoundBinary :: BV w -> BV w -> IO (BV w)
computeUpperBoundBinary BV w
l BV w
r
|
BV w
l BV w -> BV w -> Bool
forall a. Eq a => a -> a -> Bool
== BV w
r
= BV w -> IO (BV w)
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure BV w
l
|
NatRepr w -> BV w -> BV w -> BV w
forall (w :: Nat). NatRepr w -> BV w -> BV w -> BV w
BV.sub NatRepr w
w BV w
r BV w
l BV w -> BV w -> Bool
forall a. Ord a => a -> a -> Bool
< BV w
bvTwo
= do rExceedsUB <- BV w -> IO Bool
checkExceedsUpperBound BV w
r
pure $ if rExceedsUB then l else r
|
Bool
otherwise
= do let nextToTry :: BV w
nextToTry = NatRepr w -> Integer -> BV w
forall (w :: Nat). NatRepr w -> Integer -> BV w
BV.mkBV NatRepr w
w ((BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
l Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ BV w -> Integer
forall (w :: Nat). BV w -> Integer
BV.asUnsigned BV w
r) Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
2)
exceedsUB <- BV w -> IO Bool
checkExceedsUpperBound BV w
nextToTry
if exceedsUB
then computeUpperBoundBinary l nextToTry
else computeUpperBoundBinary nextToTry r
checkExceedsUpperBound :: BV w -> IO Bool
checkExceedsUpperBound :: BV w -> IO Bool
checkExceedsUpperBound BV w
bv = SolverProcess scope solver -> IO Bool -> IO Bool
forall (m :: Type -> Type) solver scope a.
(MonadIO m, MonadMask m, SMTReadWriter solver) =>
SolverProcess scope solver -> m a -> m a
WPO.inNewFrame SolverProcess scope solver
proc (IO Bool -> IO Bool) -> IO Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ do
geUpperBound <- sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
forall (w :: Nat).
(1 <= w) =>
sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
forall sym (w :: Nat).
(IsExprBuilder sym, 1 <= w) =>
sym -> SymBV sym w -> SymBV sym w -> IO (Pred sym)
WI.bvUge sym
sym SymBV sym w
symBV (Expr scope (BaseBVType w) -> IO (BoolExpr scope))
-> IO (Expr scope (BaseBVType w)) -> IO (BoolExpr scope)
forall (m :: Type -> Type) a b. Monad m => (a -> m b) -> m a -> m b
=<< sym -> NatRepr w -> BV w -> IO (SymBV sym w)
forall (w :: Nat).
(1 <= w) =>
sym -> NatRepr w -> BV w -> IO (SymBV sym w)
forall sym (w :: Nat).
(IsExprBuilder sym, 1 <= w) =>
sym -> NatRepr w -> BV w -> IO (SymBV sym w)
WI.bvLit sym
sym NatRepr w
w BV w
bv
WPS.assume conn geUpperBound
msat <- WPO.check proc "resolveSymBV (check if upper bound has been exceeded)"
case msat of
SatResult () ()
WSat.Unknown -> IO Bool
forall a. IO a
failUnknown
WSat.Sat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
False
WSat.Unsat{} -> Bool -> IO Bool
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Bool
True