1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only4{-# OPTIONS_GHC -Wno-x-partial #-}56module Symbolic (exprTests) where78import Data.Functor ((<&>))9import Data.Maybe (fromJust)10import Data.Word (Word32, Word64)11import Language.QBE.Simulator.Default.Expression qualified as DE12import Language.QBE.Simulator.Explorer (defSolver)13import Language.QBE.Simulator.Expression qualified as E14import Language.QBE.Simulator.Memory qualified as MEM15import Language.QBE.Simulator.Symbolic.Expression qualified as SE16import Language.QBE.Types qualified as QBE17import SimpleBV qualified as SMT18import Test.Tasty19import Test.Tasty.HUnit20import Test.Tasty.QuickCheck21 ( Arbitrary,22 Property,23 arbitrary,24 elements,25 ioProperty,26 testProperty,27 (==>),28 )2930getSolver :: IO SMT.Solver31getSolver = do32 s <- defSolver33 SMT.check s >> pure s3435eqConcrete :: Maybe SE.BitVector -> Maybe DE.RegVal -> IO Bool36eqConcrete (Just sym) (Just con) = do37 s <- getSolver38 symVal <- SMT.getValue s (SE.toSExpr sym)39 case (symVal, con) of40 (SMT.Bits 32 sv, DE.VWord cv) -> pure $ sv == fromIntegral cv41 (SMT.Bits 64 sv, DE.VLong cv) -> pure $ sv == fromIntegral cv42 _ -> pure False43eqConcrete Nothing Nothing = pure True44eqConcrete _ _ = pure False4546------------------------------------------------------------------------4748data UnaryInput = UnaryInput QBE.BaseType Word6449 deriving (Show)5051instance Arbitrary UnaryInput where52 arbitrary = do53 t <- elements [QBE.Word, QBE.Long]54 UnaryInput t <$> arbitrary5556unaryProp ::57 (SE.BitVector -> Maybe SE.BitVector) ->58 (DE.RegVal -> Maybe DE.RegVal) ->59 UnaryInput ->60 Property61unaryProp opSym opCon (UnaryInput ty val) = ioProperty $ do62 eqConcrete (opSym $ E.fromLit (QBE.Base ty) val) (opCon $ E.fromLit (QBE.Base ty) val)6364negEquiv :: TestTree65negEquiv = testProperty "neg" (unaryProp E.neg E.neg)6667------------------------------------------------------------------------6869data BinaryInput = BinaryInput QBE.BaseType Word64 Word6470 deriving (Show)7172instance Arbitrary BinaryInput where73 arbitrary = do74 ty <- elements [QBE.Word, QBE.Long]75 lhs <- arbitrary76 BinaryInput ty lhs <$> arbitrary7778binaryProp ::79 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->80 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->81 BinaryInput ->82 Property83binaryProp opSym opCon (BinaryInput ty lhs rhs) =84 ioProperty $85 eqConcrete (opSym (mkS lhs) (mkS rhs)) (opCon (mkC lhs) (mkC rhs))86 where87 mkS :: Word64 -> SE.BitVector88 mkS = E.fromLit (QBE.Base ty)8990 mkC :: Word64 -> DE.RegVal91 mkC = E.fromLit (QBE.Base ty)9293divProp ::94 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->95 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->96 BinaryInput ->97 Property98divProp opSym opCon input@(BinaryInput _ _ rhs) =99 (rhs /= 0) ==> binaryProp opSym opCon input100101opEquiv :: TestTree102opEquiv =103 testGroup104 "Operation equivalence"105 [ testProperty "add" (binaryProp E.add E.add),106 testProperty "sub" (binaryProp E.sub E.sub),107 testProperty "mul" (binaryProp E.mul E.mul),108 testProperty "div" (divProp E.div E.div),109 testProperty "or" (binaryProp E.or E.or),110 testProperty "xor" (binaryProp E.xor E.xor),111 testProperty "and" (binaryProp E.and E.and),112 testProperty "urem" (divProp E.urem E.urem),113 testProperty "srem" (divProp E.srem E.srem),114 testProperty "udiv" (divProp E.udiv E.udiv),115 testProperty "eq" (binaryProp E.eq E.eq),116 testProperty "ne" (binaryProp E.ne E.ne),117 testProperty "sle" (binaryProp E.sle E.sle),118 testProperty "slt" (binaryProp E.slt E.slt),119 testProperty "sge" (binaryProp E.sge E.sge),120 testProperty "sgt" (binaryProp E.sgt E.sgt),121 testProperty "ule" (binaryProp E.ule E.ule),122 testProperty "ult" (binaryProp E.ult E.ult),123 testProperty "uge" (binaryProp E.uge E.uge),124 testProperty "ugt" (binaryProp E.ugt E.ugt)125 ]126127------------------------------------------------------------------------128129data ShiftInput = ShiftInput QBE.BaseType Word64 Word32130 deriving (Show)131132instance Arbitrary ShiftInput where133 arbitrary = do134 t <- elements [QBE.Word, QBE.Long]135 v <- arbitrary136 ShiftInput t v <$> arbitrary137138shiftProp ::139 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->140 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->141 ShiftInput ->142 Property143shiftProp opSym opCon (ShiftInput ty val amount) = ioProperty $ do144 let symValue = E.fromLit (QBE.Base ty) val :: SE.BitVector145 let conValue = E.fromLit (QBE.Base ty) val :: DE.RegVal146147 let symResult = symValue `opSym` E.fromLit (QBE.Base QBE.Word) (fromIntegral amount)148 let conResult = conValue `opCon` E.fromLit (QBE.Base QBE.Word) (fromIntegral amount)149150 eqConcrete symResult conResult151152shiftEquiv :: TestTree153shiftEquiv =154 testGroup155 "Concrete and symbolic shifts are equivalent"156 [ testProperty "sar" (shiftProp E.sar E.sar),157 testProperty "shr" (shiftProp E.shr E.shr),158 testProperty "shl" (shiftProp E.shl E.shl)159 ]160161------------------------------------------------------------------------162163equivTests :: TestTree164equivTests =165 testGroup166 "QuickCheck-based equivalence tests"167 [shiftEquiv, negEquiv, opEquiv]168169storeTests :: TestTree170storeTests =171 testGroup172 "Storage Instance Tests"173 [ testCase "Create bitvector and convert it to bytes" $174 do175 s <- getSolver176 let bytes = (MEM.toBytes (E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector) :: [SE.BitVector])177 values <- mapM (SMT.getValue s . SE.toSExpr) bytes178 values @?= [SMT.Bits 8 0xef, SMT.Bits 8 0xbe, SMT.Bits 8 0xad, SMT.Bits 8 0xde],179 testCase "Convert bitvector to bytes and back" $180 do181 s <- getSolver182183 let bytes = (MEM.toBytes (E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector) :: [SE.BitVector])184 length bytes @?= 4185186 value <- case MEM.fromBytes (QBE.LBase QBE.Word) bytes of187 Just x -> SMT.getValue s (SE.toSExpr x) <&> Just188 Nothing -> pure Nothing189 value @?= Just (SMT.Bits 32 0xdeadbeef)190 ]191192valueReprTests :: TestTree193valueReprTests =194 testGroup195 "Symbolic ValueRepr Tests"196 [ testCase "Create from literal and add" $197 do198 s <- getSolver199200 let v1 = E.fromLit (QBE.Base QBE.Word) 127201 let v2 = E.fromLit (QBE.Base QBE.Word) 128202203 expr <- SMT.getValue s (SE.toSExpr $ fromJust $ v1 `E.add` v2)204 expr @?= SMT.Bits 32 0xff,205 testCase "Add incompatible values" $206 do207 let v1 = E.fromLit (QBE.Base QBE.Word) 0xffffffff :: SE.BitVector208 let v2 = E.fromLit (QBE.Base QBE.Long) 0xff :: SE.BitVector209210 -- Note: E.add doesn't do subtyping if invoked directly211 (v1 `E.add` v2) @?= Nothing,212 testCase "Subtyping" $213 do214 s <- getSolver215216 let v1 = E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector217 let v2 = E.fromLit (QBE.Base QBE.Long) 0xff :: SE.BitVector218219 let v1sub = fromJust $ E.subType QBE.Word v1220 v1sub' <- SMT.getValue s (SE.toSExpr v1sub)221 v1sub' @?= SMT.Bits 32 0xdeadbeef222223 let v2sub = fromJust $ E.subType QBE.Word v2224 v2sub' <- SMT.getValue s (SE.toSExpr v2sub)225 v2sub' @?= SMT.Bits 32 0xff226227 let subtypedAddExpr = v1sub `E.add` v2sub228 expr <- SMT.getValue s (SE.toSExpr (fromJust subtypedAddExpr))229230 expr @?= SMT.Bits 32 0xdeadbfee,231 testCase "Extend subwords" $232 do233 s <- getSolver234235 let value = E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector236237 -- let sext = fromJust $ E.wordToLong (QBE.SLSubWord QBE.SignedByte) value238 -- sextVal <- SMT.getValue s (SE.toSExpr sext)239 -- sextVal @?= SMT.Bits 64 0xffffffffffffffef240241 let zext = fromJust $ E.wordToLong (QBE.SLSubWord QBE.UnsignedByte) value242 zextVal <- SMT.getValue s (SE.toSExpr zext)243 zextVal @?= SMT.Bits 64 0xef244 ]245246exprTests :: TestTree247exprTests = testGroup "Expression tests" [storeTests, valueReprTests, equivTests]