1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: MIT AND GPL-3.0-only4{-# LANGUAGE PatternSynonyms #-}56module SimpleBV7 ( SExpr,8 SMT.Solver (..),9 SMT.defaultConfig,10 SMT.newLogger,11 SMT.newLoggerWithHandle,12 SMT.newSolver,13 SMT.newSolverWithConfig,14 SMT.solverLogger,15 SMT.smtSolverLogger,16 SMT.setLogic,17 SMT.setOption,18 SMT.push,19 SMT.pop,20 SMT.popMany,21 SMT.check,22 SMT.Result (..),23 SMT.Value (..),24 pattern W,25 pattern Byte,26 pattern Half,27 pattern Word,28 pattern Long,29 width,30 const,31 declareBV,32 assert,33 sexprToVal,34 getValue,35 getValues,36 toSMT,37 ite,38 and,39 or,40 not,41 eq,42 bvLit,43 bvAdd,44 bvAShr,45 bvLShr,46 bvAnd,47 bvMul,48 bvNeg,49 bvOr,50 bvSDiv,51 bvSLeq,52 bvSLt,53 bvSGeq,54 bvSGt,55 bvSRem,56 bvShl,57 bvSub,58 bvUDiv,59 bvULeq,60 bvUGeq,61 bvUGt,62 bvULt,63 bvURem,64 bvXOr,65 concat,66 extract,67 signExtend,68 zeroExtend,69 )70where7172import Control.DeepSeq (NFData, NFData1)73import Data.Bits (shiftL, shiftR, (.&.))74import GHC.Generics (Generic, Generic1)75import SimpleSMT qualified as SMT76import Prelude hiding (and, concat, const, not, or)7778data Expr a79 = Var String80 | Int Integer81 | And a a82 | Or a a83 | Neg a84 | Not a85 | Eq a a86 | BvAdd a a87 | BvAShr a a88 | BvLShr a a89 | BvAnd a a90 | BvMul a a91 | BvOr a a92 | BvSDiv a a93 | BvSLeq a a94 | BvSLt a a95 | BvSGeq a a96 | BvSGt a a97 | BvSRem a a98 | BvShl a a99 | BvSub a a100 | BvUDiv a a101 | BvULeq a a102 | BvUGeq a a103 | BvUGt a a104 | BvULt a a105 | BvURem a a106 | BvXOr a a107 | Concat a a108 | Ite a a a109 | Extract Int Int a110 | SignExtend Integer a111 | ZeroExtend Integer a112 deriving (Show, Eq, Generic, Generic1)113114instance (NFData a) => NFData (Expr a)115116instance NFData1 Expr117118data SExpr119 = SExpr120 { width :: Int,121 sexpr :: Expr SExpr122 }123 deriving (Show, Eq, Generic)124125instance NFData SExpr126127toSMT :: SExpr -> SMT.SExpr128toSMT expr =129 case sexpr expr of130 (Var name) -> SMT.const name131 (Int v) -> SMT.List [SMT.Atom "_", SMT.Atom ("bv" ++ show v), SMT.Atom $ show (width expr)]132 (Or lhs rhs) -> SMT.or (toSMT lhs) (toSMT rhs)133 (Ite cond lhs rhs) -> SMT.ite (toSMT cond) (toSMT lhs) (toSMT rhs)134 (And lhs rhs) -> SMT.and (toSMT lhs) (toSMT rhs)135 (Not v) -> SMT.not (toSMT v)136 (Neg v) -> SMT.bvNeg (toSMT v)137 (SignExtend n v) -> SMT.signExtend n (toSMT v)138 (ZeroExtend n v) -> SMT.zeroExtend n (toSMT v)139 (Eq lhs rhs) -> SMT.eq (toSMT lhs) (toSMT rhs)140 (Concat lhs rhs) -> SMT.concat (toSMT lhs) (toSMT rhs)141 (Extract o w e) -> SMT.extract (toSMT e) (fromIntegral $ o + w - 1) (fromIntegral o)142 (BvAnd lhs rhs) -> SMT.bvAnd (toSMT lhs) (toSMT rhs)143 (BvAShr lhs rhs) -> SMT.bvAShr (toSMT lhs) (toSMT rhs)144 (BvLShr lhs rhs) -> SMT.bvLShr (toSMT lhs) (toSMT rhs)145 (BvAdd lhs rhs) -> SMT.bvAdd (toSMT lhs) (toSMT rhs)146 (BvMul lhs rhs) -> SMT.bvMul (toSMT lhs) (toSMT rhs)147 (BvOr lhs rhs) -> SMT.bvOr (toSMT lhs) (toSMT rhs)148 (BvSDiv lhs rhs) -> SMT.bvSDiv (toSMT lhs) (toSMT rhs)149 (BvSLeq lhs rhs) -> SMT.bvSLeq (toSMT lhs) (toSMT rhs)150 (BvSLt lhs rhs) -> SMT.bvSLt (toSMT lhs) (toSMT rhs)151 (BvSGeq lhs rhs) -> SMT.fun "bvsge" [toSMT lhs, toSMT rhs]152 (BvSGt lhs rhs) -> SMT.fun "bvsgt" [toSMT lhs, toSMT rhs]153 (BvSRem lhs rhs) -> SMT.bvSRem (toSMT lhs) (toSMT rhs)154 (BvShl lhs rhs) -> SMT.bvShl (toSMT lhs) (toSMT rhs)155 (BvSub lhs rhs) -> SMT.bvSub (toSMT lhs) (toSMT rhs)156 (BvUDiv lhs rhs) -> SMT.bvUDiv (toSMT lhs) (toSMT rhs)157 (BvULeq lhs rhs) -> SMT.bvULeq (toSMT lhs) (toSMT rhs)158 (BvUGeq lhs rhs) -> SMT.fun "bvuge" [toSMT lhs, toSMT rhs]159 (BvUGt lhs rhs) -> SMT.fun "bvugt" [toSMT lhs, toSMT rhs]160 (BvULt lhs rhs) -> SMT.bvULt (toSMT lhs) (toSMT rhs)161 (BvURem lhs rhs) -> SMT.bvURem (toSMT lhs) (toSMT rhs)162 (BvXOr lhs rhs) -> SMT.bvXOr (toSMT lhs) (toSMT rhs)163164boolWidth :: Int165boolWidth = 1166167pattern E :: Expr SExpr -> SExpr168pattern E expr <- SExpr {sexpr = expr, width = _}169170pattern W :: Int -> SExpr171pattern W w <- SExpr {width = w}172173pattern Byte :: SExpr174pattern Byte <- SExpr {width = 8}175176pattern Half :: SExpr177pattern Half <- SExpr {width = 16}178179pattern Word :: SExpr180pattern Word <- SExpr {width = 32}181182pattern Long :: SExpr183pattern Long <- SExpr {width = 64}184185------------------------------------------------------------------------186187const :: String -> Int -> SExpr188const name width = SExpr width (Var name)189190declareBV :: SMT.Solver -> String -> Int -> IO SExpr191declareBV solver name width = do192 let bits = SMT.tBits $ fromIntegral width193 SMT.declare solver name bits >> pure (const name width)194195bvLit :: Int -> Integer -> SExpr196bvLit width value = SExpr width (Int value)197198sexprToVal :: SExpr -> SMT.Value199sexprToVal (E (Var n)) = SMT.Other $ SMT.Atom n200sexprToVal e@(E (Int i)) = SMT.Bits (width e) i201sexprToVal _ = SMT.Other $ SMT.Atom "_"202203assert :: SMT.Solver -> SExpr -> IO ()204assert solver = SMT.assert solver . toSMT205206getValue :: SMT.Solver -> SExpr -> IO SMT.Value207getValue solver = SMT.getExpr solver . toSMT208209getValues :: SMT.Solver -> [SExpr] -> IO [(String, SMT.Value)]210getValues solver exprs = do211 map go <$> SMT.getExprs solver (map toSMT exprs)212 where213 go :: (SMT.SExpr, SMT.Value) -> (String, SMT.Value)214 go (SMT.Atom name, value) = (name, value)215 go _ = error "non-atomic variable in inputVars"216217---------------------------------------------------------------------------218219ite :: SExpr -> SExpr -> SExpr -> SExpr220ite cond ifT ifF = SExpr (width ifT) (Ite cond ifT ifF)221222not :: SExpr -> SExpr223not (E (Not cond)) = cond224not expr = expr {sexpr = Not expr}225226and :: SExpr -> SExpr -> SExpr227and lhs rhs = lhs {sexpr = And lhs rhs}228229or :: SExpr -> SExpr -> SExpr230or lhs rhs = lhs {sexpr = Or lhs rhs}231232signExtend :: Integer -> SExpr -> SExpr233signExtend n expr = SExpr (width expr + fromIntegral n) $ SignExtend n expr234235zeroExtend :: Integer -> SExpr -> SExpr236zeroExtend n expr = SExpr (width expr + fromIntegral n) $ ZeroExtend n expr237238------------------------------------------------------------------------239240eq' :: SExpr -> SExpr -> SExpr241eq' lhs rhs = SExpr boolWidth $ Eq lhs rhs242243-- Eliminates ITE expressions when comparing with constants values, this is244-- useful in the QBE context to eliminate comparisons with truth values.245eq :: SExpr -> SExpr -> SExpr246eq lexpr@(E (Ite cond (E (Int ifT)) (E (Int ifF)))) rexpr@(E (Int other))247 | other == ifT = cond248 | other == ifF = not cond249 | otherwise = eq' lexpr rexpr250eq lhs rhs = eq' lhs rhs251252concat' :: SExpr -> SExpr -> SExpr253concat' lhs rhs =254 SExpr (width lhs + width rhs) $ Concat lhs rhs255256-- Replace 0 concats with zero extension: (concat (_ bv0 8) buf6)257concatZeros :: SExpr -> SExpr -> SExpr258concatZeros lhs@(E (Int 0)) rhs = zeroExtend (fromIntegral $ width lhs) rhs259concatZeros lhs rhs = concat' lhs rhs260261-- Replaces continuous concat expressions with a single extract expression.262concat :: SExpr -> SExpr -> SExpr263concat264 lhs@(E (Extract loff lwidth latom@(E exprLhs)))265 rhs@(E (Extract roff rwidth (E exprRhs)))266 | exprLhs == exprRhs && (roff + rwidth) == loff = extract latom roff (lwidth + rwidth)267 | otherwise = concatZeros lhs rhs268concat lhs rhs = concatZeros lhs rhs269270extract' :: SExpr -> Int -> Int -> SExpr271extract' expr off w = SExpr w $ Extract off w expr272273-- Eliminate extract expression where the value already has the desired bits.274extractSameWidth :: SExpr -> Int -> Int -> SExpr275extractSameWidth expr off w276 | off == 0 && width expr == w = expr277 | otherwise = extract' expr off w278279-- Eliminate nested extract expression of the same width.280extractNested :: SExpr -> Int -> Int -> SExpr281extractNested expr@(E (Extract ioff iwidth _)) off width =282 if ioff == off && iwidth == width283 then expr284 else extractSameWidth expr off width285extractNested expr off width = extractSameWidth expr off width286287-- Performs direct extractions of constant immediate values.288extractConst :: SExpr -> Int -> Int -> SExpr289extractConst (E (Int value)) off w =290 SExpr w . Int $ truncTo (value `shiftR` off) w291 where292 truncTo v bits = v .&. ((1 `shiftL` bits) - 1)293extractConst expr off width = extractNested expr off width294295-- This performs constant propagation for subtyping of condition values (i.e.296-- the conversion from long to word).297extractIte :: SExpr -> Int -> Int -> SExpr298extractIte (E (Ite cond ifT@(E (Int _)) ifF@(E (Int _)))) off w =299 let ex x = extractConst x off w300 in SExpr w $ Ite cond (ex ifT) (ex ifF)301extractIte expr off width = extractConst expr off width302303extractZeros ::304 SExpr ->305 Int ->306 Int ->307 SExpr308extractZeros expr@(E (ZeroExtend extBits inner)) exOff exWidth309 | exOff >= width inner && extBits > 0 = bvLit exWidth 0 -- only extracting zeros310 | otherwise = extractIte expr exOff exWidth311extractZeros outer exOff exWidth = extractIte outer exOff exWidth312313extractExt' ::314 (Integer -> SExpr -> Expr SExpr) ->315 SExpr ->316 Integer ->317 SExpr ->318 Int ->319 Int ->320 SExpr321extractExt' cons outer extBits inner exOff exWidth322 -- If we are only extracting the non-extended bytes...323 | width inner >= exOff + exWidth = extractZeros inner exOff exWidth324 -- Consider: ((_ extract 31 0) ((_ zero_extend 56) byte))325 | exWidth < fromIntegral extBits && exOff == 0 =326 SExpr exWidth $ cons (extBits - fromIntegral exWidth) inner327 -- No folding...328 | otherwise = extractZeros outer exOff exWidth329330-- Remove ZeroExtend and SignExtend expression where we don't use331-- the extended bits because we extract below the extended size.332extractExt :: SExpr -> Int -> Int -> SExpr333extractExt expr@(E (SignExtend extBits inner)) exOff exWidth =334 extractExt' SignExtend expr extBits inner exOff exWidth335extractExt expr@(E (ZeroExtend extBits inner)) exOff exWidth =336 extractExt' ZeroExtend expr extBits inner exOff exWidth337extractExt expr off w = extractIte expr off w338339extract :: SExpr -> Int -> Int -> SExpr340extract = extractExt341342------------------------------------------------------------------------343344binOp' :: (SExpr -> SExpr -> Expr SExpr) -> SExpr -> SExpr -> SExpr345binOp' op lhs rhs = lhs {sexpr = op lhs rhs}346347binOp :: (SExpr -> SExpr -> Expr SExpr) -> SExpr -> SExpr -> SExpr348-- Consider: (bvslt ((_ zero_extend 24) byte0) ((_ zero_extend 24) byte1))349-- TODO: The following only works if 'op' does not consider sign-bits. Otherwise,350-- there is no semantic expression equivalence after this folding operation.351-- binOp op lhs@(E (ZeroExtend _ lhsInner)) rhs@(E (ZeroExtend _ rhsInner)) =352-- if width lhsInner == width rhsInner353-- then binOp op lhsInner rhsInner354-- else binOp' op lhs rhs355binOp op lhs rhs = binOp' op lhs rhs356357-- TODO: Generate these using template-haskell.358359bvNeg :: SExpr -> SExpr360bvNeg x = x {sexpr = Neg x}361362bvAdd :: SExpr -> SExpr -> SExpr363bvAdd = binOp BvAdd364365bvAShr :: SExpr -> SExpr -> SExpr366bvAShr = binOp BvAShr367368bvLShr :: SExpr -> SExpr -> SExpr369bvLShr = binOp BvLShr370371bvAnd :: SExpr -> SExpr -> SExpr372bvAnd = binOp BvAnd373374bvMul :: SExpr -> SExpr -> SExpr375bvMul = binOp BvMul376377bvOr :: SExpr -> SExpr -> SExpr378bvOr = binOp BvOr379380bvSDiv :: SExpr -> SExpr -> SExpr381bvSDiv = binOp BvSDiv382383bvSLeq :: SExpr -> SExpr -> SExpr384bvSLeq = binOp BvSLeq385386bvSLt :: SExpr -> SExpr -> SExpr387bvSLt = binOp BvSLt388389bvSGeq :: SExpr -> SExpr -> SExpr390bvSGeq = binOp BvSGeq391392bvSGt :: SExpr -> SExpr -> SExpr393bvSGt = binOp BvSGt394395bvSRem :: SExpr -> SExpr -> SExpr396bvSRem = binOp BvSRem397398bvShl :: SExpr -> SExpr -> SExpr399bvShl = binOp BvShl400401bvSub :: SExpr -> SExpr -> SExpr402bvSub = binOp BvSub403404bvUDiv :: SExpr -> SExpr -> SExpr405bvUDiv = binOp BvUDiv406407bvULeq :: SExpr -> SExpr -> SExpr408bvULeq = binOp BvULeq409410bvUGeq :: SExpr -> SExpr -> SExpr411bvUGeq = binOp BvUGeq412413bvUGt :: SExpr -> SExpr -> SExpr414bvUGt = binOp BvUGt415416bvULt :: SExpr -> SExpr -> SExpr417bvULt = binOp BvULt418419bvURem :: SExpr -> SExpr -> SExpr420-- Fold constant bvURem operations which are emitted a lot in our generated421-- SMT-LIB because of QBE's "shift-value modulo bitsize"-semantics.422bvURem vlhs@(E (Int lhs)) (E (Int rhs)) =423 SExpr (width vlhs) $424 -- XXX: On urem-by-zero, SMT-LIB returns the lhs.425 if rhs == 0426 then Int lhs427 else Int $ lhs `rem` rhs428bvURem lhs rhs = binOp BvURem lhs rhs429430bvXOr :: SExpr -> SExpr -> SExpr431bvXOr = binOp BvXOr