1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>2--3-- SPDX-License-Identifier: GPL-3.0-only45module Symbolic (exprTests) where67import Data.Functor ((<&>))8import Data.Int (Int64)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 )2829getSolver :: IO SMT.Solver30getSolver = do31 s <- defSolver32 SMT.check s >> pure s3334eqConcrete :: Maybe SE.BitVector -> Maybe DE.RegVal -> IO Bool35eqConcrete (Just sym) (Just con) = do36 s <- getSolver37 symVal <- SMT.getValue s (SE.toSExpr sym)38 case (symVal, con) of39 (SMT.Bits 32 sv, DE.VWord cv) -> pure $ sv == fromIntegral cv40 (SMT.Bits 64 sv, DE.VLong cv) -> pure $ sv == fromIntegral cv41 _ -> pure False42eqConcrete Nothing Nothing = pure True43eqConcrete _ _ = pure False4445------------------------------------------------------------------------4647data UnaryInput = UnaryInput QBE.BaseType Word6448 deriving (Show)4950instance Arbitrary UnaryInput where51 arbitrary = do52 t <- elements [QBE.Word, QBE.Long]53 UnaryInput t <$> arbitrary5455unaryProp ::56 (SE.BitVector -> Maybe SE.BitVector) ->57 (DE.RegVal -> Maybe DE.RegVal) ->58 UnaryInput ->59 Property60unaryProp opSym opCon (UnaryInput ty val) = ioProperty $ do61 eqConcrete (opSym $ E.fromLit (QBE.Base ty) val) (opCon $ E.fromLit (QBE.Base ty) val)6263negEquiv :: TestTree64negEquiv = testProperty "neg" (unaryProp E.neg E.neg)6566------------------------------------------------------------------------6768data BinaryInput = BinaryInput QBE.BaseType Word64 Word6469 deriving (Show)7071instance Arbitrary BinaryInput where72 arbitrary = do73 ty <- elements [QBE.Word, QBE.Long]74 lhs <- arbitrary75 BinaryInput ty lhs <$> arbitrary7677binaryEq ::78 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->79 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->80 BinaryInput ->81 IO Bool82binaryEq opSym opCon (BinaryInput ty lhs rhs) =83 eqConcrete (opSym (mkS lhs) (mkS rhs)) (opCon (mkC lhs) (mkC rhs))84 where85 mkS :: Word64 -> SE.BitVector86 mkS = E.fromLit (QBE.Base ty)8788 mkC :: Word64 -> DE.RegVal89 mkC = E.fromLit (QBE.Base ty)9091binaryProp ::92 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->93 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->94 BinaryInput ->95 Property96binaryProp opSym opCon input = ioProperty $ binaryEq opSym opCon input9798opEquiv :: TestTree99opEquiv =100 testGroup101 "Operation equivalence"102 [ testProperty "add" (binaryProp E.add E.add),103 testProperty "sub" (binaryProp E.sub E.sub),104 testProperty "mul" (binaryProp E.mul E.mul),105 testProperty "div" (binaryProp E.div E.div),106 testProperty "or" (binaryProp E.or E.or),107 testProperty "xor" (binaryProp E.xor E.xor),108 testProperty "and" (binaryProp E.and E.and),109 testProperty "urem" (binaryProp E.urem E.urem),110 testProperty "srem" (binaryProp E.srem E.srem),111 testProperty "udiv" (binaryProp E.udiv E.udiv),112 testProperty "eq" (binaryProp E.eq E.eq),113 testProperty "ne" (binaryProp E.ne E.ne),114 testProperty "sle" (binaryProp E.sle E.sle),115 testProperty "slt" (binaryProp E.slt E.slt),116 testProperty "sge" (binaryProp E.sge E.sge),117 testProperty "sgt" (binaryProp E.sgt E.sgt),118 testProperty "ule" (binaryProp E.ule E.ule),119 testProperty "ult" (binaryProp E.ult E.ult),120 testProperty "uge" (binaryProp E.uge E.uge),121 testProperty "ugt" (binaryProp E.ugt E.ugt)122 ]123124------------------------------------------------------------------------125126data ShiftInput = ShiftInput QBE.BaseType Word64 Word32127 deriving (Show)128129instance Arbitrary ShiftInput where130 arbitrary = do131 t <- elements [QBE.Word, QBE.Long]132 v <- arbitrary133 ShiftInput t v <$> arbitrary134135shiftProp ::136 (SE.BitVector -> SE.BitVector -> Maybe SE.BitVector) ->137 (DE.RegVal -> DE.RegVal -> Maybe DE.RegVal) ->138 ShiftInput ->139 Property140shiftProp opSym opCon (ShiftInput ty val amount) = ioProperty $ do141 let symValue = E.fromLit (QBE.Base ty) val :: SE.BitVector142 let conValue = E.fromLit (QBE.Base ty) val :: DE.RegVal143144 let symResult = symValue `opSym` E.fromLit (QBE.Base QBE.Word) (fromIntegral amount)145 let conResult = conValue `opCon` E.fromLit (QBE.Base QBE.Word) (fromIntegral amount)146147 eqConcrete symResult conResult148149shiftEquiv :: TestTree150shiftEquiv =151 testGroup152 "Concrete and symbolic shifts are equivalent"153 [ testProperty "sar" (shiftProp E.sar E.sar),154 testProperty "shr" (shiftProp E.shr E.shr),155 testProperty "shl" (shiftProp E.shl E.shl)156 ]157158------------------------------------------------------------------------159160equivTests :: TestTree161equivTests =162 testGroup163 "Equivalence tests for symbolic and default expression interpreter"164 [ shiftEquiv,165 negEquiv,166 opEquiv,167 -- Occurs when the most-negative integer is divided by -1.168 testCase "Signed division overflow on div" $169 do170 let input =171 BinaryInput QBE.Long 0x8000000000000000 $172 fromIntegral (-1 :: Int64)173 binaryEq E.div E.div input >>= assertBool "signed-div-overflow",174 testCase "Signed division overflow on srem" $175 do176 let input =177 BinaryInput QBE.Long 0x8000000000000000 $178 fromIntegral (-1 :: Int64)179 binaryEq E.srem E.srem input >>= assertBool "signed-srem-overflow"180 ]181182storeTests :: TestTree183storeTests =184 testGroup185 "Storage Instance Tests"186 [ testCase "Create bitvector and convert it to bytes" $187 do188 s <- getSolver189 let bytes = (MEM.toBytes (E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector) :: [SE.BitVector])190 values <- mapM (SMT.getValue s . SE.toSExpr) bytes191 values @?= [SMT.Bits 8 0xef, SMT.Bits 8 0xbe, SMT.Bits 8 0xad, SMT.Bits 8 0xde],192 testCase "Convert bitvector to bytes and back" $193 do194 s <- getSolver195196 let bytes = (MEM.toBytes (E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector) :: [SE.BitVector])197 length bytes @?= 4198199 value <- case MEM.fromBytes (QBE.LBase QBE.Word) bytes of200 Just x -> SMT.getValue s (SE.toSExpr x) <&> Just201 Nothing -> pure Nothing202 value @?= Just (SMT.Bits 32 0xdeadbeef)203 ]204205valueReprTests :: TestTree206valueReprTests =207 testGroup208 "Symbolic ValueRepr Tests"209 [ testCase "create from literal and add" $210 do211 s <- getSolver212213 let v1 = E.fromLit (QBE.Base QBE.Word) 127214 let v2 = E.fromLit (QBE.Base QBE.Word) 128215216 expr <- SMT.getValue s (SE.toSExpr $ fromJust $ v1 `E.add` v2)217 expr @?= SMT.Bits 32 0xff,218 testCase "add incompatible values" $219 do220 let v1 = E.fromLit (QBE.Base QBE.Word) 0xffffffff :: SE.BitVector221 let v2 = E.fromLit (QBE.Base QBE.Long) 0xff :: SE.BitVector222223 -- Note: E.add doesn't do subtyping if invoked directly224 (v1 `E.add` v2) @?= Nothing,225 testCase "extend" $226 do227 s <- getSolver228229 let v1 = E.fromLit QBE.Byte 0xff :: SE.BitVector230 ext1 = fromJust $ E.extend QBE.HalfWord False v1231 ext1Val <- SMT.getValue s (SE.toSExpr ext1)232 ext1Val @?= SMT.Bits 16 0x00ff233234 let v2 = E.fromLit QBE.Byte 0xab :: SE.BitVector235 ext2 = fromJust $ E.extend (QBE.Base QBE.Word) True v2236 ext2Val <- SMT.getValue s (SE.toSExpr ext2)237 ext2Val @?= SMT.Bits 32 0xffffffab238239 let v3 = E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector240 E.extend (QBE.Base QBE.Word) True v3 @?= Nothing241 E.extend QBE.Byte True v3 @?= Nothing,242 testCase "extract" $243 do244 s <- getSolver245246 let value = E.fromLit (QBE.Base QBE.Word) 0xdeadbeef :: SE.BitVector247248 let ex1 = fromJust $ E.extract QBE.Byte value249 ex1Val <- SMT.getValue s (SE.toSExpr ex1)250 ex1Val @?= SMT.Bits 8 0xef251252 let ex2 = fromJust $ E.extract QBE.HalfWord value253 ex2Val <- SMT.getValue s (SE.toSExpr ex2)254 ex2Val @?= SMT.Bits 16 0xbeef255256 E.extract (QBE.Base QBE.Word) value @?= Just value257 E.extract (QBE.Base QBE.Long) value @?= Nothing258 ]259260exprTests :: TestTree261exprTests = testGroup "Expression tests" [storeTests, valueReprTests, equivTests]