packages feed

rme-what4-0.1: src/Data/RME/What4.hs

{-# Language LambdaCase, GADTs, ImportQualifiedPost, BlockArguments, TypeFamilies, RankNTypes #-}
{-|
Module      : Data.RME.What4
Description : What4 solver adapter for the RME backend.
Copyright   : (c) 2025 Galois
License     : BSD3
Maintainer  : cryptol@galois.com

This module implements a What4 solver adapter that translates What4 expressions
into RME (Reed–Muller expansion) terms and uses the RME backend for
symbolic reasoning.

Reference:
  * https://en.wikipedia.org/wiki/Reed–Muller_expansion

-}
module Data.RME.What4 (rmeAdapter) where

import Control.Monad (replicateM, ap, (<$!>))
import Data.BitVector.Sized qualified as BV
import Data.IntSet (IntSet)
import Data.IntSet qualified as IntSet
import Data.Parameterized.Map qualified as MapF
import Data.Parameterized.NatRepr ( NatRepr(..) )
import Data.Parameterized.Nonce qualified as Nonce
import Data.RME
import Data.Vector qualified as V
import What4.Expr.App qualified as W4
import What4.Expr.BoolMap qualified as W4
import What4.Expr.Builder qualified as W4
import What4.Expr.GroundEval qualified as W4
import What4.Expr.WeightedSum qualified as Sum
import What4.Expr.UnaryBV qualified as UnaryBV
import What4.Interface qualified as W4
import What4.SatResult qualified as W4
import What4.SemiRing qualified as W4
import What4.Solver

rmeAdapter :: SolverAdapter st
rmeAdapter =
  SolverAdapter
  { solver_adapter_name = "RME"
  , solver_adapter_config_options = []
  , solver_adapter_check_sat = rmeAdapterCheckSat
  , solver_adapter_write_smt2 = \_ _ _ -> pure ()
  }

rmeAdapterCheckSat ::
  W4.ExprBuilder t st fs ->
  LogData ->
  [W4.BoolExpr t] ->
  (SatResult (W4.GroundEvalFn t, Maybe (ExprRangeBindings t)) () -> IO a) ->
  IO a
rmeAdapterCheckSat _ logger asserts k =
 do logCallback logger "Starting RME"
    let m = foldl conj true <$!> traverse evalExpr asserts
    case runM m of
      Left e ->
       do logCallback logger e
          putStrLn e
          k W4.Unknown
      Right (rme, s) ->
        case sat rme of
          Nothing -> k (W4.Unsat ())
          Just model ->
            let trueVars = IntSet.fromList [i | (i, True) <- model]
            in k (W4.Sat (W4.GroundEvalFn (groundEval trueVars (nonceCache s)), Nothing))

-- | Ground evaluation function. Given a satisfying assignment (set of true variables)
-- this function will used the cached results to evaluate an expression.
groundEval :: IntSet -> MapF.MapF (Nonce.Nonce t) SomeR -> W4.Expr t tp -> IO (W4.GroundValue tp)
groundEval trueVars nonces e =
  let t = W4.exprType e
      ev x = eval x (`IntSet.member` trueVars)
  in
  case flip MapF.lookup nonces =<< W4.exprMaybeId e of
    Just (SomeR n)
      | W4.BaseBoolRepr <- t -> pure $! ev n
      | W4.BaseBVRepr w <- t -> pure $! bitsToBV w (fmap ev n)
    _ -> W4.evalGroundExpr (groundEval trueVars nonces) e

bitsToBV :: Foldable f => NatRepr w -> f Bool -> BV.BV w
bitsToBV w bs = BV.mkBV w (foldl (\acc x -> if x then 1 + acc*2 else acc*2) 0 bs)

newtype M t a = M { unM :: forall k. S t -> (String -> k) -> (a -> S t -> k) -> k }

runM :: M t a -> Either String (a, S t)
runM m = unM m emptyS Left (curry Right)

instance Functor (M t) where
  fmap f (M m) = M (\s e k -> m s e (k . f))

instance Applicative (M t) where
  pure x = M (\s _ k -> k x s)
  (<*>) = ap

instance Monad (M t) where
  M m1 >>= f = M (\s0 e t -> m1 s0 e (\a s1 -> unM (f a) s1 e t))

instance MonadFail (M t) where
  fail str = M (\_ e _ -> e str)

-- | Get the current evaluation state
get :: M t (S t)
get = M (\s _ t -> t s s)

-- | Set the current evaluation state
set :: S t -> M t ()
set s = M (\_ _ t -> t () s)

-- | The state of evaluating an Expr into an RME term
data S t = S
  { nextVar :: !Int -- ^ next fresh variable to be used with RME lit
  , nonceCache :: !(MapF.MapF (Nonce.Nonce t) SomeR) -- ^ previously translated w4 expressions
  }

-- | The initial evaluation state
emptyS :: S t
emptyS = S
  { nextVar = 0
  , nonceCache = MapF.empty
  }

-- | Produce a fresh RME term
freshRME :: M t RME
freshRME =
 do s <- get
    if nextVar s == maxBound then
      fail "Fresh variables exhausted"
    else do
      set $! s{ nextVar = nextVar s + 1 }
      pure (lit (nextVar s))

-- | Map what4 base types to RME representations
type family R (t :: W4.BaseType) where
  R W4.BaseBoolType = RME
  R (W4.BaseBVType n) = RMEV

-- | Newtype wrapper for 'R' type for use with 'MapF'
newtype SomeR tp = SomeR (R tp)

-- | Representation type use to determine which RME representation is being used
data RMERepr (t :: W4.BaseType) where
  -- | A single RME bit
  BitRepr :: RMERepr W4.BaseBoolType
  -- | A vector of w RME bits
  BVRepr  :: !Int -> RMERepr (W4.BaseBVType w)

-- | Helper for memoizing evaluation. Given a nonced and a way to evaluation
-- action this will either return the cached value for that nonce or
-- evaluate the given action and store it in the cache before returning it.
cached :: Nonce.Nonce t tp -> M t (R tp) -> M t (R tp)
cached nonce gen =
 do mb <- fmap (MapF.lookup nonce . nonceCache) get
    case mb of
      Just (SomeR r) -> pure r
      Nothing ->
       do r <- gen
          s <- get
          set s{ nonceCache = MapF.insert nonce (SomeR r) (nonceCache s) }
          pure r

-- | A version of what4's SemiRingRepr that matches the semi-rings that this backend supports
data SemiRingRepr sr where
  SemiRingRepr :: !(W4.BVFlavorRepr fv) -> !Int -> SemiRingRepr (W4.SemiRingBV fv w)

-- | Converts a BV width into the Int type used by Vector.
-- In the extreme case that the NatRepr is out of range of
-- Int, this operation will fail.
evalWidth :: NatRepr w -> M t Int
evalWidth w =
  let n = natValue w in
  if n > fromIntegral (maxBound :: Int)
    then fail "Bit-vector width too wide!"
    else pure (fromIntegral n)

-- | Convert a generic what4 base type to an RME base-type.
-- Reports an error for unsupported base types.
evalTypeRepr :: W4.BaseTypeRepr tp -> M t (RMERepr tp)
evalTypeRepr = \case
  W4.BaseBoolRepr -> pure BitRepr
  W4.BaseBVRepr w ->
   do w' <- evalWidth w
      pure $! BVRepr w'
  r -> fail ("RME does not support " ++ show r)

-- | Convert a generic what4 semiring type to an RME semiring type.
-- Reports an error for unsupported semiring types.
evalSemiRingRepr :: W4.SemiRingRepr sr -> M t (SemiRingRepr sr)
evalSemiRingRepr = \case
      W4.SemiRingIntegerRepr -> fail "RME does not support integers"
      W4.SemiRingRealRepr -> fail "RME does not support real numbers"
      W4.SemiRingBVRepr flv w ->
       do w' <- evalWidth w
          pure $! SemiRingRepr flv w'

-- | Evaluate an expression, if possible, into an RME term.
evalExpr :: W4.Expr t tp -> M t (R tp)
evalExpr = \case
  W4.BoolExpr x _ -> pure $! constant x
  W4.AppExpr x -> cached (W4.appExprId x) (evalApp (W4.appExprApp x))
  W4.BoundVarExpr x -> cached (W4.bvarId x) (allocateVar =<< evalTypeRepr (W4.bvarType x))
  W4.SemiRingLiteral rpr c _ ->
   do SemiRingRepr _ w <- evalSemiRingRepr rpr
      case c of
        BV.BV ci -> pure $! integer w ci
  W4.FloatExpr{} -> fail "RME does not support floating point numbers"
  W4.StringExpr{} -> fail "RME does not support string literals"
  W4.NonceAppExpr{} -> fail "RME does not support quantifiers"

-- | Allocates an unconstrainted RME term at the given type.
allocateVar :: RMERepr tp -> M t (R tp)
allocateVar = \case
  BitRepr -> freshRME
  BVRepr w -> V.fromList <$!> replicateM w freshRME

-- | Convert a what4 App into an RME term for the operations that the
-- RME backend supports.
evalApp :: W4.App (W4.Expr t) tp -> M t (R tp)
evalApp = \case

  W4.BaseEq rpr x y ->
   do x1 <- evalExpr x
      y1 <- evalExpr y
      r <- evalTypeRepr rpr
      pure $! case r of
        BitRepr -> iff x1 y1
        BVRepr{} -> eq x1 y1

  W4.BaseIte rpr _ b t e ->
   do b1 <- evalExpr b
      t1 <- evalExpr t
      e1 <- evalExpr e
      r <- evalTypeRepr rpr
      pure $! case r of
        BitRepr -> mux b1 t1 e1
        BVRepr{} -> V.zipWith (mux b1) t1 e1

  W4.NotPred x ->
   do x1 <- evalExpr x
      pure $! compl x1

  W4.ConjPred c ->
    case W4.viewConjMap c of
      W4.ConjTrue -> pure true
      W4.ConjFalse -> pure false
      W4.Conjuncts y ->
       do let f (x, W4.Positive) = evalExpr x
              f (x, W4.Negative) = compl <$!> evalExpr x
          foldl1 conj <$!> traverse f y

  W4.BVTestBit i ve ->
   do v <- evalExpr ve
      pure $! v V.! (length v - fromIntegral i - 1) -- little-endian index

  W4.BVSlt x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! slt x' y'

  W4.BVUlt x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! ult x' y'

  W4.BVConcat _ x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! x' <> y'

  W4.BVShl _ x y ->
    do x' <- evalExpr x
       y' <- evalExpr y
       pure $! shl x' y'

  W4.BVCountTrailingZeros _ v -> countTrailingZeros <$!> evalExpr v

  W4.BVCountLeadingZeros _ v -> countLeadingZeros <$!> evalExpr v

  W4.BVPopcount _ v -> popcount <$!> evalExpr v

  W4.BVOrBits w s ->
   do vs <- traverse evalExpr (W4.bvOrToList s)
      w' <- evalWidth w
      pure $! foldl (V.zipWith disj) (V.replicate w' false) vs

  W4.BVSelect i n v ->
   do v' <- evalExpr v
      i' <- evalWidth i
      n' <- evalWidth n
      let start = length v' - n' - i' -- i is given as a little endian index
      pure $! V.take n' (V.drop start v')

  W4.BVFill w b ->
   do w' <- evalWidth w
      b' <- evalExpr b
      pure $! V.replicate w' b'

  W4.BVLshr _ x i ->
   do x' <- evalExpr x
      i' <- evalExpr i
      pure $! lshr x' i'

  W4.BVAshr _ x i ->
   do x' <- evalExpr x
      i' <- evalExpr i
      pure $! ashr x' i'

  W4.BVRol _ x i ->
   do x' <- evalExpr x
      i' <- evalExpr i
      pure $! rol x' i'

  W4.BVRor _ x i ->
   do x' <- evalExpr x
      i' <- evalExpr i
      pure $! ror x' i'

  W4.BVZext w v ->
   do v' <- evalExpr v
      w' <- evalWidth w
      let l = w' - length v'
      pure (V.replicate l false <> v')

  W4.BVSext w v ->
   do v' <- evalExpr v
      w' <- evalWidth w
      let l = w' - length v'
      pure (V.replicate l (V.head v') <> v')

  W4.SemiRingSum s ->
   do SemiRingRepr flv w <- evalSemiRingRepr (Sum.sumRepr s)

      case flv of
        -- modular addition
        W4.BVArithRepr ->
          Sum.evalM
            (\x y -> pure $! add x y)
            (\(BV.BV c) r ->
             do v <- evalExpr r
                pure $! mul v (integer w c))
            (\(BV.BV c) -> pure $! integer w c)
            s

        -- bitwise xor
        W4.BVBitsRepr ->
          Sum.evalM
            (\x y -> pure $! V.zipWith xor x y)
            (\(BV.BV c) r ->
             do v <- evalExpr r
                pure $! V.zipWith conj (integer w c) v)
            (\(BV.BV c) -> pure $! integer w c)
            s

  W4.SemiRingProd p ->
   do SemiRingRepr flv w <- evalSemiRingRepr (Sum.prodRepr p)

      case flv of
      -- arithmetic multiplication
        W4.BVArithRepr ->
         do mb <- Sum.prodEvalM
              (\x y -> pure $! mul x y)
              evalExpr
              p
            pure $! case mb of
              Nothing -> integer w 1
              Just r -> r

        -- bitwise conjunction
        W4.BVBitsRepr ->
         do mb <- Sum.prodEvalM
                  (\x y -> pure $! V.zipWith conj x y)
                  evalExpr
                  p
            pure $! case mb of
              Nothing -> V.replicate w true -- ~0
              Just r -> r

  W4.BVUdiv _ x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! udiv x' y'

  W4.BVUrem _ x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! urem x' y'

  W4.BVSdiv _ x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! sdiv x' y'

  W4.BVSrem _ x y ->
   do x' <- evalExpr x
      y' <- evalExpr y
      pure $! srem x' y'

  W4.BVUnaryTerm u ->
   do let constEval x =
           do x' <- evalExpr x
              case isBool x' of
                Nothing -> fail "Unary term not constant"
                Just r -> pure r
      w' <- evalWidth (UnaryBV.width u)
      u' <- UnaryBV.evaluate constEval u
      pure $! integer w' u'

  e -> fail ("RME does not support " ++ show e)