crucible-symio-0.1: src/What4/CachedArray.hs
-----------------------------------------------------------------------
-- |
-- Module : What4.CachedArray
-- Description : What4 array storage with a concrete backing supporting symbolic indexes
-- Copyright : (c) Galois, Inc 2020
-- License : BSD3
-- Maintainer : Daniel Matichuk <dmatichuk@galois.com>
-- Stability : provisional
--
--
-- This module provides a storage structure that supports arrays that have reads
-- from and writes to a mix of concrete and symbolic indexes. It can be thought
-- of as a multi-dimensional array that supports reading and writing contiguous
-- "chunks". It is built on the 'Data.Parameterized.IntervalsMap' structure,
-- which computes an abstract domain over indexes (supporting symbolic
-- reads/writes).
------------------------------------------------------------------------
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module What4.CachedArray
(
CachedArray
, ArrayChunk
, mkArrayChunk
, evalChunk
, writeChunk
, writeSingle
, readSingle
, readChunk
, arrayToChunk
, chunkToArray
, muxArrays
, initArrayConcrete
, initArray
) where
import Control.Lens ( (.~), (&) )
import Control.Monad ( foldM, join )
import Control.Monad.Trans ( lift )
import Data.Functor.Const
import Data.Maybe ( catMaybes )
import qualified Data.Map as Map
import Data.Maybe (mapMaybe)
import qualified Data.IORef as IO
import qualified Data.Parameterized.TraversableFC as FC
import qualified Data.Parameterized.Context as Ctx
import Data.Parameterized.Classes
import Data.Parameterized.NatRepr ( type (<=) )
import qualified Data.Parameterized.Nonce as PN
import qualified Data.BitVector.Sized as BV
import qualified Lang.Crucible.Utils.MuxTree as MT
import qualified What4.Interface as W4
import qualified What4.Partial as W4
import qualified What4.Concrete as W4
import qualified What4.Utils.AbstractDomains as W4
import qualified What4.Utils.BVDomain as BVD
import qualified Data.Parameterized.IntervalsMap as IM
import Data.Parameterized.IntervalsMap ( AsOrd(..) )
------------------------------------------------
-- Interface
-- TODO: add coalescing function for merging adjacent entries
newtype ArrayChunk sym idx tp =
ArrayChunk { evalChunk :: (W4.SymExpr sym idx -> IO (W4.SymExpr sym tp)) }
mkArrayChunk ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
(W4.SymExpr sym idx -> IO (W4.SymExpr sym tp)) ->
IO (ArrayChunk sym idx tp)
mkArrayChunk _sym f = do
ref <- IO.newIORef Map.empty
let f' idx = do
m <- IO.readIORef ref
case Map.lookup (AsOrd idx) m of
Just v -> return v
Nothing -> do
v <- f idx
IO.modifyIORef ref (Map.insert (AsOrd idx) v)
return v
return $ ArrayChunk f'
writeChunk ::
forall sym ctx tp.
NonEmptyCtx ctx =>
W4.IsSymExprBuilder sym =>
sym ->
-- | base address to write to
Ctx.Assignment (W4.SymExpr sym) ctx ->
-- | size of write
W4.SymExpr sym (CtxFirst ctx) ->
-- | symbolic value to write
ArrayChunk sym (CtxFirst ctx) tp ->
CachedArray sym ctx tp ->
IO (CachedArray sym ctx tp)
writeChunk sym loExpr offExpr chunk arr | NonEmptyCtxRepr <- nonEmptyCtxRepr @_ @ctx =
arrConstraints arr $ do
rng <- mkSymRangeOff sym loExpr offExpr
arr' <- invalidateEntries sym rng arr
-- offset the incoming function so that its value at zero becomes the value at
-- the base address
let
off = indexToOffset $ symRangeLo rng
vals :: SymIndex sym ctx -> IO (W4.PartExpr (W4.Pred sym) (W4.SymExpr sym tp))
vals idx' = do
p <- isInRange sym rng idx'
(SymOffset idxOffsetExpr) <- indexToOffset <$> subSymOffset sym idx' off
v <- evalChunk chunk idxOffsetExpr
return $ W4.mkPE p v
entry <- mkMultiEntry vals
arr'' <- IM.insertWithM (mergeEntriesMux sym (isInRange sym rng)) (symRangeToAbs rng) (toPMuxTree sym entry) (arrMap arr')
incNonce $ arr { arrMap = arr''}
writeSingle ::
forall sym ctx tp.
W4.IsSymExprBuilder sym =>
sym ->
Ctx.Assignment (W4.SymExpr sym) ctx ->
W4.SymExpr sym tp ->
CachedArray sym ctx tp ->
IO (CachedArray sym ctx tp)
writeSingle sym symIdxExpr val arr = arrConstraints arr $ do
arr' <- invalidateEntries sym (SymRangeSingle symIdx) arr
entry <- mkValEntry sym symIdx val
arr'' <- IM.insertWithM (mergeEntriesMux sym (isEqIndex sym symIdx)) (symIdxToAbs symIdx) (toPMuxTree sym entry) (arrMap arr')
incNonce $ arr { arrMap = arr'' }
where
symIdx = mkSymIndex symIdxExpr
readSingle ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
Ctx.Assignment (W4.SymExpr sym) idx ->
CachedArray sym idx tp ->
IO (W4.SymExpr sym tp)
readSingle sym symIdxExpr arr = readArrayBase sym symIdx arr
where
symIdx = SymIndex symIdxExpr Nothing
readChunk ::
forall sym ctx tp.
NonEmptyCtx ctx =>
W4.IsSymExprBuilder sym =>
sym ->
-- | base address to read from
Ctx.Assignment (W4.SymExpr sym) ctx ->
-- | size of read
W4.SymExpr sym (CtxFirst ctx) ->
CachedArray sym ctx tp ->
IO (ArrayChunk sym (CtxFirst ctx) tp)
readChunk sym loExpr offExpr arr | NonEmptyCtxRepr <- nonEmptyCtxRepr @_ @ctx = do
rng <- mkSymRangeOff sym loExpr offExpr
let absIdx = symRangeToAbs rng
-- offset the outgoing array so that its value at zero is the value at
-- the base address
return $ ArrayChunk $ \idxExpr -> do
let off = SymOffset idxExpr
offsetIdx <- addSymOffset sym (symRangeLo rng) off
readArrayBase sym (offsetIdx { symIdxAbs = Just absIdx}) arr
chunkToArray ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
W4.BaseTypeRepr idx ->
ArrayChunk sym idx tp ->
IO (W4.SymArray sym (Ctx.EmptyCtx Ctx.::> idx) tp)
chunkToArray sym repr chunk = do
var <- W4.freshBoundVar sym W4.emptySymbol repr
body <- evalChunk chunk (W4.varExpr sym var)
fn <- W4.definedFn sym (W4.safeSymbol "readRange") (Ctx.empty Ctx.:> var) body W4.AlwaysUnfold
W4.arrayFromFn sym fn
arrayToChunk ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
(W4.SymArray sym (Ctx.EmptyCtx Ctx.::> idx) tp) ->
IO (ArrayChunk sym idx tp)
arrayToChunk sym arr = mkArrayChunk sym $ \idx -> W4.arrayLookup sym arr (Ctx.empty Ctx.:> idx)
muxArrays ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
W4.Pred sym ->
CachedArray sym idx tp ->
CachedArray sym idx tp ->
IO (CachedArray sym idx tp)
muxArrays sym p arr1 arr2 = case arr1 == arr2 of
True -> return arr1
False -> arrConstraints arr1 $ do
notp <- W4.notPred sym p
arr' <- IM.mergeWithM
(pmuxTreeAddCondition sym p)
(pmuxTreeAddCondition sym notp)
(muxEntries sym p)
(arrMap arr1)
(arrMap arr2)
incNonce $ arr1 { arrMap = arr' }
-- | Initialize an array with symbolic contents at concrete locations
initArrayConcrete ::
forall sym idx tp idx' tp'.
W4.IsSymExprBuilder sym =>
idx ~ (idx' Ctx.::> tp') =>
sym ->
W4.BaseTypeRepr tp ->
[(Ctx.Assignment W4.ConcreteVal idx, W4.SymExpr sym tp)] ->
IO (CachedArray sym idx tp)
initArrayConcrete sym repr m = do
nonce <- freshArrayNonce
im <- IM.fromList <$> mapM go m
return $ CachedArray im (\x -> x) repr nonce
where
go ::
(Ctx.Assignment W4.ConcreteVal idx, W4.SymExpr sym tp) ->
IO (AbsIndex idx, PMuxTree sym (ArrayEntry sym idx tp))
go (cidx, v) = do
symIdx <- concreteIdxToSym sym cidx
entry <- mkValEntry sym symIdx v
return $ (symIdxToAbs symIdx, toPMuxTree sym entry)
-- | Initialize an array with symbolic contents at symbolic locations
initArray ::
forall sym idx tp idx' tp'.
W4.IsSymExprBuilder sym =>
idx ~ (idx' Ctx.::> tp') =>
sym ->
W4.BaseTypeRepr tp ->
[(Ctx.Assignment (W4.SymExpr sym) idx, W4.SymExpr sym tp)] ->
IO (CachedArray sym idx tp)
initArray sym repr m = do
nonce <- freshArrayNonce
im <- IM.fromList <$> mapM go m
return $ CachedArray im (\x -> x) repr nonce
where
go ::
(Ctx.Assignment (W4.SymExpr sym) idx, W4.SymExpr sym tp) ->
IO (AbsIndex idx, PMuxTree sym (ArrayEntry sym idx tp))
go (symIdxExpr, v) = do
let
symIdx = SymIndex symIdxExpr Nothing
entry <- mkValEntry sym symIdx v
return $ (symIdxToAbs symIdx, toPMuxTree sym entry)
---------------------------------------------------
-- Implementation
-- | A sentinel nonce that is refreshed every time the array is updated.
newtype ArrayNonce = ArrayNonce (PN.Nonce PN.GlobalNonceGenerator IO)
instance Eq ArrayNonce where
(ArrayNonce i1) == (ArrayNonce i2) | Just Refl <- testEquality i1 i2 = True
_ == _ = False
instance Ord ArrayNonce where
compare (ArrayNonce i1) (ArrayNonce i2) = toOrdering $ compareF i1 i2
freshArrayNonce :: IO ArrayNonce
freshArrayNonce = ArrayNonce <$> PN.freshNonce PN.globalNonceGenerator
-- | An array that supports reading from a stack of mixed concrete/symbolic writes efficiently.
--
-- The primary interface is intended to be 'readChunk' and 'writeChunk', which
-- allow writing contiguous subsequences of data to the array.
--
-- Note that the equality instances is based on a unique nonce (see
-- 'ArrayNonce') that is incremented each time the array is updated, and is thus
-- an identity test rather than a structural equality test.
data CachedArray sym (ctx :: Ctx.Ctx W4.BaseType) (tp :: W4.BaseType) where
CachedArray ::
{
arrMap :: IM.IntervalsMap AbsIntervalEnd ctx (PMuxTree sym (ArrayEntry sym ctx tp))
, arrConstraints :: forall a. (NonEmptyCtx ctx => a) -> a
, arrTypeRepr :: W4.BaseTypeRepr tp
, _arrNonce :: ArrayNonce
} -> CachedArray sym ctx tp
instance Eq (CachedArray sym idx tp) where
(CachedArray _ _ _ nonce1) == (CachedArray _ _ _ nonce2) = nonce1 == nonce2
incNonce ::
CachedArray sym idx tp ->
IO (CachedArray sym idx tp)
incNonce (CachedArray am ac tr _) = do
nonce <- freshArrayNonce
return $ CachedArray am ac tr nonce
-- | An array entry defines a set of possible values for a given
-- abstract domain. Entries may overlap, and so as an invariant we
-- preserve the fact that at each logical index, exactly one entry is valid
data ArrayEntry sym ctx tp where
ArrayEntry ::
{ -- TODO: should we cache these results?
entryVals :: (SymIndex sym ctx -> IO (W4.PartExpr (W4.Pred sym) (W4.SymExpr sym tp)))
, entryNonce :: ArrayNonce
} -> ArrayEntry sym ctx tp
incNonceEntry ::
ArrayEntry sym ctx tp ->
IO (ArrayEntry sym ctx tp)
incNonceEntry (ArrayEntry vals _) = do
nonce <- freshArrayNonce
return $ ArrayEntry vals nonce
instance Eq (ArrayEntry sym ctx tp) where
e1 == e2 = entryNonce e1 == entryNonce e2
instance Ord (ArrayEntry sym ctx tp) where
compare e1 e2 = compare (entryNonce e1) (entryNonce e2)
-- | A symbolic index into the array. It represents the index for a single array element,
-- although its value may be symbolic
data SymIndex sym ctx =
SymIndex
{ -- | the symbolic index
_symIdxExpr :: Ctx.Assignment (W4.SymExpr sym) ctx
-- | an optional override for the abstract domain of the index
, symIdxAbs :: Maybe (AbsIndex ctx)
}
deriving instance W4.IsSymExprBuilder sym => Eq (SymIndex sym ctx)
deriving instance W4.IsSymExprBuilder sym => Ord (SymIndex sym ctx)
-- | An offset is an index into the last element of the array index
-- A value range is always representable as a base + offset
newtype SymOffset sym ctx where
SymOffset :: W4.SymExpr sym (CtxFirst ctx) -> SymOffset sym ctx
newtype FirstIndex ctx where
FirstIndex :: Ctx.Index ctx (CtxFirst ctx) -> FirstIndex ctx
skipFirst ::
FirstIndex (ctx Ctx.::> tp1) -> FirstIndex (ctx Ctx.::> tp1 Ctx.::> tp2)
skipFirst (FirstIndex idx) = FirstIndex (Ctx.skipIndex idx)
firstIndex ::
forall ctx.
NonEmptyCtx ctx =>
Ctx.Size ctx ->
FirstIndex ctx
firstIndex sz | NonEmptyCtxRepr <- nonEmptyCtxRepr @_ @ctx =
case Ctx.viewSize (Ctx.decSize sz) of
Ctx.ZeroSize -> FirstIndex (Ctx.baseIndex)
Ctx.IncSize _ -> skipFirst (firstIndex (Ctx.decSize sz))
indexToOffset ::
forall sym ctx.
NonEmptyCtx ctx =>
W4.IsSymExprBuilder sym =>
SymIndex sym ctx ->
SymOffset sym ctx
indexToOffset (SymIndex eCtx _) =
let
FirstIndex idx = firstIndex (Ctx.size eCtx)
e = eCtx Ctx.! idx
in SymOffset e
addSymOffset ::
forall sym ctx.
W4.IsSymExprBuilder sym =>
NonEmptyCtx ctx =>
sym ->
SymIndex sym ctx ->
SymOffset sym ctx ->
IO (SymIndex sym ctx)
addSymOffset sym (SymIndex eCtx _) (SymOffset off) = do
let
FirstIndex idx = firstIndex (Ctx.size eCtx)
e = eCtx Ctx.! idx
e' <- case W4.exprType off of
W4.BaseIntegerRepr -> W4.intAdd sym e off
W4.BaseBVRepr _ -> W4.bvAdd sym e off
_ -> fail $ "Unsupported type"
return $ SymIndex (eCtx & (ixF idx) .~ e') Nothing
negateSymOffset ::
W4.IsSymExprBuilder sym =>
sym ->
SymOffset sym ctx ->
IO (SymOffset sym ctx)
negateSymOffset sym (SymOffset off) = do
e' <- case W4.exprType off of
W4.BaseIntegerRepr -> W4.intNeg sym off
W4.BaseBVRepr _ -> W4.bvNeg sym off
_ -> fail $ "Unsupported type"
return $ SymOffset e'
-- | Previous offset from the given one, to create an exclusive upper bound
prevSymOffset ::
W4.IsSymExprBuilder sym =>
sym ->
SymOffset sym ctx ->
IO (SymOffset sym ctx)
prevSymOffset sym (SymOffset off) = do
e' <- case W4.exprType off of
W4.BaseIntegerRepr -> do
one <- W4.intLit sym 1
W4.intSub sym off one
W4.BaseBVRepr w -> do
one <- W4.bvLit sym w (BV.mkBV w 1)
W4.bvSub sym off one
_ -> fail $ "Unsupported type"
return $ SymOffset e'
subSymOffset ::
W4.IsSymExprBuilder sym =>
NonEmptyCtx ctx =>
sym ->
SymIndex sym ctx ->
SymOffset sym ctx ->
IO (SymIndex sym ctx)
subSymOffset sym idx off = do
negoff <- negateSymOffset sym off
addSymOffset sym idx negoff
mkSymIndex ::
forall sym ctx.
Ctx.Assignment (W4.SymExpr sym) ctx ->
SymIndex sym ctx
mkSymIndex e = SymIndex e Nothing
-- | Represents a symbolic range, where equality and ordering is defined on the
-- abstract domain of the underlying expression
data SymRange sym ctx =
SymRangeSingle (SymIndex sym ctx)
| SymRangeMulti (SymIndex sym ctx) (SymIndex sym ctx)
symRangeLo :: SymRange sym ctx -> SymIndex sym ctx
symRangeLo (SymRangeSingle symIdx) = symIdx
symRangeLo (SymRangeMulti loIdx _) = loIdx
symRangeToAbs ::
W4.IsSymExprBuilder sym =>
SymRange sym ctx ->
AbsIndex ctx
symRangeToAbs (SymRangeSingle symIdx) = symIdxToAbs symIdx
symRangeToAbs (SymRangeMulti loIdx hiIdx) =
joinAbsIndex (symIdxToAbs loIdx) (symIdxToAbs hiIdx)
-- | Create a range that is exclusive of the given offset
-- i.e. 2 + 4 --> (2, 5)
mkSymRangeOff ::
forall sym ctx .
W4.IsSymExprBuilder sym =>
NonEmptyCtx ctx =>
sym ->
Ctx.Assignment (W4.SymExpr sym) ctx ->
W4.SymExpr sym (CtxFirst ctx) ->
IO (SymRange sym ctx)
mkSymRangeOff sym loExpr offExpr = do
let
lo = mkSymIndex @sym loExpr
off = SymOffset offExpr
offPrev <- prevSymOffset sym off
hi <- addSymOffset sym lo offPrev
return $ (SymRangeMulti lo hi)
data NonEmptyCtxRepr (ctx :: Ctx.Ctx k) where
NonEmptyCtxRepr :: NonEmptyCtxRepr (ctx Ctx.::> x)
type family CtxFirst (ctx :: Ctx.Ctx k) where
CtxFirst (Ctx.EmptyCtx Ctx.::> a) = a
CtxFirst (ctx Ctx.::> _) = CtxFirst ctx
class NonEmptyCtx (ctx :: Ctx.Ctx k) where
type CtxHead ctx :: k
type CtxTail ctx :: Ctx.Ctx k
nonEmptyCtxRepr :: NonEmptyCtxRepr ctx
instance NonEmptyCtx (ctx Ctx.::> tp) where
type CtxHead (ctx Ctx.::> tp) = tp
type CtxTail (ctx Ctx.::> tp) = ctx
nonEmptyCtxRepr = NonEmptyCtxRepr
data AbsIntervalEnd tp where
AbsIntervalEndInt :: W4.ValueBound Integer -> AbsIntervalEnd W4.BaseIntegerType
AbsIntervalEndBV :: (1 <= w) => W4.NatRepr w -> W4.ValueBound Integer -> AbsIntervalEnd (W4.BaseBVType w)
instance Ord (AbsIntervalEnd tp) where
compare a1 a2 = toOrdering $ compareF a1 a2
instance Eq (AbsIntervalEnd tp) where
a1 == a2 = (compare a1 a2) == EQ
instance TestEquality AbsIntervalEnd where
testEquality a1 a2 = case compareF a1 a2 of
EQF -> Just Refl
_ -> Nothing
instance OrdF AbsIntervalEnd where
compareF a1 a2 = case (a1, a2) of
(AbsIntervalEndInt n1, AbsIntervalEndInt n2) -> fromOrdering $ compare n1 n2
(AbsIntervalEndBV w1 i1, AbsIntervalEndBV w2 i2) ->
lexCompareF w1 w2 $ fromOrdering $ compare i1 i2
(AbsIntervalEndInt{}, AbsIntervalEndBV{}) -> LTF
(AbsIntervalEndBV{}, AbsIntervalEndInt{}) -> GTF
type AbsIndex (idx :: Ctx.Ctx W4.BaseType) = IM.Intervals AbsIntervalEnd idx
type AbsInterval tp = IM.IntervalF AbsIntervalEnd tp
bvDomainRange ::
1 <= w =>
W4.NatRepr w ->
BVD.BVDomain w ->
AbsInterval (W4.BaseBVType w)
bvDomainRange w d = case BVD.ubounds d of
(i1, i2) -> IM.mkIntervalF $ IM.ClosedInterval (AbsIntervalEndBV w (W4.Inclusive i1)) (AbsIntervalEndBV w (W4.Inclusive i2))
exprToAbsInterval ::
forall sym tp.
W4.IsSymExprBuilder sym =>
W4.SymExpr sym tp ->
AbsInterval tp
exprToAbsInterval e = absToInterval (W4.exprType e) (W4.getAbsValue e)
absToInterval ::
W4.BaseTypeRepr tp ->
W4.AbstractValue tp ->
AbsInterval tp
absToInterval repr v = case repr of
W4.BaseIntegerRepr -> case v of
W4.SingleRange x -> IM.mkIntervalF $ IM.ClosedInterval (AbsIntervalEndInt (W4.Inclusive x)) (AbsIntervalEndInt (W4.Inclusive x))
W4.MultiRange lo hi -> IM.mkIntervalF $ IM.ClosedInterval (AbsIntervalEndInt lo) (AbsIntervalEndInt hi)
W4.BaseBVRepr w -> bvDomainRange w v
_ -> error "Unsupported type"
readArrayBase ::
forall sym idx tp.
W4.IsSymExprBuilder sym =>
sym ->
SymIndex sym idx ->
CachedArray sym idx tp ->
IO (W4.SymExpr sym tp)
readArrayBase sym symIdx arr = do
let
intersecting = IM.toList $ IM.intersecting (arrMap arr) (symIdxToAbs symIdx)
entries <- mapM expandEntry $ concat $ map (viewPMuxTree . snd) intersecting
case entries of
[(W4.PE p (AsOrd e), path_cond)]
| Just True <- W4.asConstantPred path_cond
, Just True <- W4.asConstantPred p -> return e
entryExprs -> arrConstraints arr $ do
muxTree <- mkPMuxTreePartial sym entryExprs
MT.collapseMuxTree sym ite muxTree >>= \case
Just (AsOrd e) -> return e
-- garbage result
Nothing -> W4.freshConstant sym W4.emptySymbol (arrTypeRepr arr)
where
ite ::
W4.Pred sym ->
Maybe (AsOrd (W4.SymExpr sym) tp) ->
Maybe (AsOrd (W4.SymExpr sym) tp) ->
IO (Maybe (AsOrd (W4.SymExpr sym) tp))
ite p (Just (AsOrd e1)) (Just (AsOrd e2)) = (Just . AsOrd) <$> W4.baseTypeIte sym p e1 e2
ite _ Nothing (Just e2) = return $ Just e2
ite _ (Just e1) Nothing = return $ Just e1
ite _ Nothing Nothing = return Nothing
expandEntry ::
(ArrayEntry sym idx tp, W4.Pred sym) ->
IO (W4.PartExpr (W4.Pred sym) (AsOrd (W4.SymExpr sym) tp), W4.Pred sym)
expandEntry (entry, path_cond) = do
val <- entryVals entry symIdx
return $ (fmap AsOrd val, path_cond)
mkValEntry ::
W4.IsSymExprBuilder sym =>
sym ->
SymIndex sym ctx ->
W4.SymExpr sym tp ->
IO (ArrayEntry sym ctx tp)
mkValEntry sym idx v = do
let vals idx' = do
p <- isEqIndex sym idx idx'
return $ W4.mkPE p v
mkMultiEntry vals
mkMultiEntry ::
(SymIndex sym ctx -> IO (W4.PartExpr (W4.Pred sym) (W4.SymExpr sym tp))) ->
IO (ArrayEntry sym ctx tp)
mkMultiEntry vals = do
nonce <- freshArrayNonce
return $ ArrayEntry vals nonce
symIdxToAbs ::
forall sym ctx.
W4.IsSymExprBuilder sym =>
SymIndex sym ctx -> AbsIndex ctx
symIdxToAbs (SymIndex symIdxExpr Nothing) = IM.Intervals $ FC.fmapFC (exprToAbsInterval @sym) symIdxExpr
symIdxToAbs (SymIndex _ (Just absIdx)) = absIdx
concreteIdxToSym ::
forall sym ctx.
W4.IsSymExprBuilder sym =>
sym ->
Ctx.Assignment W4.ConcreteVal ctx ->
IO (SymIndex sym ctx)
concreteIdxToSym sym conc = do
symIdxExpr <- FC.traverseFC (W4.concreteToSym sym) conc
return $ SymIndex symIdxExpr Nothing
-- | Invalidate all entries within the given range
-- TODO: delete entries which are statically invalid
invalidateRange ::
forall sym ctx tp.
W4.IsSymExprBuilder sym =>
sym ->
-- | range to invalidate
SymRange sym ctx ->
ArrayEntry sym ctx tp ->
IO (Maybe (ArrayEntry sym ctx tp))
invalidateRange sym invalid_rng entry = do
let vals symIdx' = do
notThis <- W4.notPred sym =<< isInRange sym invalid_rng symIdx'
val <- entryVals entry symIdx'
W4.runPartialT sym notThis $ W4.returnPartial val
entry' <- incNonceEntry $ entry { entryVals = vals }
return $ Just entry'
isInRange ::
forall sym ctx.
W4.IsSymExprBuilder sym =>
sym ->
SymRange sym ctx ->
SymIndex sym ctx ->
IO (W4.Pred sym)
isInRange sym rng symIdx2@(SymIndex symIdxExpr _) = case rng of
SymRangeSingle symIdx1 -> isEqIndex sym symIdx1 symIdx2
SymRangeMulti (SymIndex loIdxExpr _) (SymIndex hiIdxExpr _) -> do
lo <- FC.toListFC getConst <$> Ctx.zipWithM doLe loIdxExpr symIdxExpr
hi <- FC.toListFC getConst <$> Ctx.zipWithM doLe symIdxExpr hiIdxExpr
foldM (W4.andPred sym) (W4.truePred sym) $ lo ++ hi
where
doLe ::
forall tp.
W4.SymExpr sym tp ->
W4.SymExpr sym tp ->
IO (Const (W4.Pred sym) tp)
doLe e1 e2 = Const <$> case W4.exprType e1 of
W4.BaseBVRepr _ -> W4.bvUle sym e1 e2
W4.BaseIntegerRepr -> W4.intLe sym e1 e2
_ -> fail "isInRange: unsupported type"
isEqIndex ::
forall sym ctx.
W4.IsSymExprBuilder sym =>
sym ->
SymIndex sym ctx ->
SymIndex sym ctx ->
IO (W4.Pred sym)
isEqIndex sym (SymIndex symIdxExpr1 _) (SymIndex symIdxExpr2 _) = do
preds <- FC.toListFC getConst <$> Ctx.zipWithM (\e1 e2 -> Const <$> W4.isEq sym e1 e2) symIdxExpr1 symIdxExpr2
foldM (W4.andPred sym) (W4.truePred sym) preds
-- | Invalidate all existing symbolic entries at exactly this index
invalidateEntries ::
forall sym ctx tp.
W4.IsSymExprBuilder sym =>
sym ->
SymRange sym ctx ->
CachedArray sym ctx tp ->
IO (CachedArray sym ctx tp)
invalidateEntries sym symRange arr = arrConstraints arr $ do
NonEmptyCtxRepr <- return $ nonEmptyCtxRepr @_ @ctx
cmap <- IM.mapMIntersecting absIndex (\v -> getMaybe <$> pmuxTreeMaybeOp sym (invalidateRange sym symRange) v) (arrMap arr)
return $ arr { arrMap = cmap }
where
absIndex = symRangeToAbs symRange
getMaybe :: PMuxTree sym (ArrayEntry sym ctx tp) -> Maybe (PMuxTree sym (ArrayEntry sym ctx tp))
getMaybe mt | isEmptyPMuxTree mt = Nothing
getMaybe mt = Just mt
buildMuxTree :: (W4.IsExprBuilder sym, Ord a) => sym -> a -> [(a, W4.Pred sym)] -> IO (MT.MuxTree sym a)
buildMuxTree sym a as =
foldM (\mt (a',p) -> MT.mergeMuxTree sym p (MT.toMuxTree sym a') mt) (MT.toMuxTree sym a) as
joinAbsIndex ::
AbsIndex ctx ->
AbsIndex ctx ->
AbsIndex ctx
joinAbsIndex (IM.Intervals idx1) (IM.Intervals idx2) = IM.Intervals $ Ctx.zipWith IM.mergeIntervalsF idx1 idx2
muxEntries ::
W4.IsSymExprBuilder sym =>
sym ->
W4.Pred sym ->
PMuxTree sym (ArrayEntry sym ctx tp) ->
PMuxTree sym (ArrayEntry sym ctx tp) ->
IO (PMuxTree sym (ArrayEntry sym ctx tp))
muxEntries sym p mtT mtF = MT.mergeMuxTree sym p mtT mtF
mergeEntries ::
forall sym ctx tp.
W4.IsSymExprBuilder sym =>
NonEmptyCtx ctx =>
sym ->
(SymIndex sym ctx -> IO (W4.Pred sym)) ->
ArrayEntry sym ctx tp ->
ArrayEntry sym ctx tp ->
IO (ArrayEntry sym ctx tp)
mergeEntries sym pickLeftFn e1 e2 = do
let vals symIdx' = do
pickLeft <- pickLeftFn symIdx'
val1 <- entryVals e1 symIdx'
val2 <- entryVals e2 symIdx'
W4.mergePartial sym (\p a b -> lift $ W4.baseTypeIte sym p a b)
pickLeft val1 val2
incNonceEntry $ e1 { entryVals = vals }
mergeEntriesMux ::
forall sym ctx tp.
W4.IsSymExprBuilder sym =>
NonEmptyCtx ctx =>
sym ->
(SymIndex sym ctx -> IO (W4.Pred sym)) ->
PMuxTree sym (ArrayEntry sym ctx tp) ->
PMuxTree sym (ArrayEntry sym ctx tp) ->
IO (PMuxTree sym (ArrayEntry sym ctx tp))
mergeEntriesMux sym pickLeftFn = pmuxTreeBinOp sym (mergeEntries sym pickLeftFn)
-- | A partial mux tree
type PMuxTree sym tp = MT.MuxTree sym (Maybe tp)
viewPMuxTree :: forall sym a. PMuxTree sym a -> [(a, W4.Pred sym)]
viewPMuxTree mt = mapMaybe go $ MT.viewMuxTree mt
where
go :: (Maybe a, W4.Pred sym) -> Maybe (a, W4.Pred sym)
go (Just a, p) = Just (a, p)
go _ = Nothing
isEmptyPMuxTree :: PMuxTree sym tp -> Bool
isEmptyPMuxTree mt = case MT.viewMuxTree mt of
[(Nothing, _)] -> True
_ -> False
mkPMuxTree ::
(W4.IsExprBuilder sym, Ord a) =>
sym ->
[(a, W4.Pred sym)] ->
IO (PMuxTree sym a)
mkPMuxTree sym ls = buildMuxTree sym Nothing (map (\(a, p) -> (Just a, p)) ls)
mkPMuxTreePartial ::
forall sym a.
(W4.IsExprBuilder sym, Ord a) =>
sym ->
[(W4.PartExpr (W4.Pred sym) a, W4.Pred sym)] ->
IO (PMuxTree sym a)
mkPMuxTreePartial sym ls = mkPMuxTree sym =<< (catMaybes <$> mapM go ls)
where
go :: (W4.PartExpr (W4.Pred sym) a, W4.Pred sym) -> IO (Maybe (a, W4.Pred sym))
go (W4.PE p a, cond) = do
p' <- W4.andPred sym p cond
return $ Just (a, p')
go (W4.Unassigned, _) = return Nothing
pmuxTreeAddCondition ::
forall sym a.
W4.IsExprBuilder sym =>
Ord a =>
sym ->
W4.Pred sym ->
PMuxTree sym a ->
IO (PMuxTree sym a)
pmuxTreeAddCondition sym cond mt = mkPMuxTree sym =<< mapM addCond (viewPMuxTree mt)
where
addCond :: (a, W4.Pred sym) -> IO (a, W4.Pred sym)
addCond (a, cond') = do
cond'' <- W4.andPred sym cond cond'
return $ (a, cond'')
pmuxTreeMaybeOp ::
(W4.IsExprBuilder sym, Ord a, Ord b) =>
sym ->
(a -> IO (Maybe b)) ->
PMuxTree sym a ->
IO (PMuxTree sym b)
pmuxTreeMaybeOp sym f mt = MT.muxTreeUnaryOp sym (\a -> join <$> mapM f a) mt
_pmuxTreeUnaryOp ::
(W4.IsExprBuilder sym, Ord b) =>
sym ->
(a -> IO b) ->
PMuxTree sym a ->
IO (PMuxTree sym b)
_pmuxTreeUnaryOp sym f mt = MT.muxTreeUnaryOp sym (\a -> mapM f a) mt
pmuxTreeBinOp ::
forall sym a b c.
(W4.IsExprBuilder sym, Ord c) =>
sym ->
(a -> b -> IO c) ->
PMuxTree sym a ->
PMuxTree sym b ->
IO (PMuxTree sym c)
pmuxTreeBinOp sym f mt1 mt2 = MT.muxTreeBinOp sym g mt1 mt2
where
g :: Maybe a -> Maybe b -> IO (Maybe c)
g (Just a) (Just b) = Just <$> f a b
g _ _ = return Nothing
toPMuxTree :: W4.IsExprBuilder sym => sym -> a -> PMuxTree sym a
toPMuxTree sym a = MT.toMuxTree sym (Just a)