packages feed

llvm-tf-9.2: test/Test/Marshal.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE Rank2Types #-}
module Test.Marshal (testsRoundTrip, testsExtract) where

import qualified LLVM.ExecutionEngine as EE
import qualified LLVM.Util.Optimize as Opt
import qualified LLVM.Util.Proxy as LP
import qualified LLVM.Core as LLVM

import qualified Type.Data.Num.Decimal as TypeNum
import Type.Base.Proxy (Proxy(Proxy))

import Foreign.Ptr (FunPtr, Ptr, nullPtr, plusPtr, castPtr)

import qualified Data.Foldable as Fold
import Data.Word (Word8, Word16, Word32, Word64)
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Tuple.HT (mapPair, mapFst)

import qualified Test.QuickCheck.Monadic as QCMon
import qualified Test.QuickCheck as QC

import Control.Monad (liftM2, void, (<=<))



type RoundTrip a = a -> QC.Property
type RoundTripVec n a = RoundTrip (LLVM.Vector n a)

roundTrip :: (EE.Marshal a, Eq a) => RoundTrip a
roundTrip x =
   QCMon.monadicIO $ do
      y <- QCMon.run $ EE.with x EE.peek
      QCMon.assert $ x==y

testsRoundTrip :: [(String, QC.Property)]
testsRoundTrip =
   map (mapFst ("RoundTrip." ++)) $
   ("f32", QC.property (roundTrip :: RoundTrip Float)) :
   ("f64", QC.property (roundTrip :: RoundTrip Double)) :
   ("i1", QC.property (roundTrip :: RoundTrip Bool)) :
   ("i2", QC.property (roundTrip :: RoundTrip Int2)) :
   ("i3", QC.property (roundTrip :: RoundTrip Int3)) :
   ("i24", QC.property (roundTrip :: RoundTrip Int24)) :
   ("i64", QC.property (roundTrip :: RoundTrip Int64)) :
   ("i2", QC.property (roundTrip :: RoundTrip Word2)) :
   ("i3", QC.property (roundTrip :: RoundTrip Word3)) :
   ("i17", QC.property (roundTrip :: RoundTrip Word17)) :
   ("i32", QC.property (roundTrip :: RoundTrip Word32)) :
   ("ptr", QC.property ((roundTrip :: RoundTrip (Ptr Word8)) . plusPtr nullPtr)) :
   ("()", QC.property (roundTrip :: RoundTrip (LLVM.Struct ()))) :
   ("struct-i8",
      QC.property (roundTrip :: RoundTrip (LLVM.Struct (Word8,())))) :
   ("struct-i8-i24",
      QC.property (roundTrip :: RoundTrip (LLVM.Struct (Word8,(Int24,()))))) :
   ("struct-i3-f32",
      QC.property (roundTrip :: RoundTrip (LLVM.Struct (Int3,(Float,()))))) :
   ("struct-i16-i1-i64",
      QC.property
         (roundTrip :: RoundTrip (LLVM.Struct (Int16,(Bool,(Word64,())))))) :
   ("v8f32", QC.property (roundTrip :: RoundTripVec TypeNum.D8 Float)) :
   ("v5f64", QC.property (roundTrip :: RoundTripVec TypeNum.D5 Double)) :
   ("v7i1", QC.property (roundTrip :: RoundTripVec TypeNum.D7 Bool)) :
   ("v13i1", QC.property (roundTrip :: RoundTripVec TypeNum.D13 Bool)) :
   ("v4i2", QC.property (roundTrip :: RoundTripVec TypeNum.D4 Int2)) :
   ("v10i2", QC.property (roundTrip :: RoundTripVec TypeNum.D10 Word2)) :
   ("v7i3", QC.property (roundTrip :: RoundTripVec TypeNum.D7 Int3)) :
   ("v5i3", QC.property (roundTrip :: RoundTripVec TypeNum.D5 Word3)) :
   ("v9i24", QC.property (roundTrip :: RoundTripVec TypeNum.D9 Int24)) :
   ("v3i17", QC.property (roundTrip :: RoundTripVec TypeNum.D3 Word17)) :
   ("v5i8", QC.property (roundTrip :: RoundTripVec TypeNum.D5 Word8)) :
   ("v3i16", QC.property (roundTrip :: RoundTripVec TypeNum.D3 Word16)) :
   ("v4i8", QC.property (roundTrip :: RoundTripVec TypeNum.D4 Int8)) :
   ("v7i32", QC.property (roundTrip :: RoundTripVec TypeNum.D7 Int32)) :
   []


type Importer func = FunPtr func -> func

generateFunction ::
   EE.ExecutionFunction f =>
   Importer f -> LLVM.CodeGenModule (LLVM.Function f) -> IO f
generateFunction imprt code = do
   td <- EE.getTargetData
   (m,func) <-
      LLVM.createModule $ do
         LLVM.setTarget LLVM.hostTriple
         LLVM.setDataLayout $ EE.dataLayoutStr td
         liftM2 (,) LLVM.getModule code
   LLVM.writeBitcodeToFile "Test.bc" m
   void $ Opt.optimizeModule 3 m
   LLVM.writeBitcodeToFile "TestOpt.bc" m
   EE.runEngineAccessWithModule m $ EE.getExecutionFunction imprt func


foreign import ccall safe "dynamic" derefTestCasePtr ::
   Importer (LLVM.Ptr inp -> LLVM.Ptr out -> IO ())

modul ::
   (LLVM.IsType inp, LLVM.IsType out) =>
   (LLVM.Value inp -> LLVM.CodeGenFunction () (LLVM.Value out)) ->
   LLVM.CodeGenModule (LLVM.Function (LLVM.Ptr inp -> LLVM.Ptr out -> IO ()))
modul codegen =
   LLVM.createFunction LLVM.ExternalLinkage $ \xPtr yPtr -> do
      flip LLVM.store yPtr =<< codegen =<< LLVM.load xPtr
      LLVM.ret ()

run ::
   (Show inp, EE.Marshal inp, EE.Marshal out) =>
   QC.Gen inp ->
   (LLVM.Value inp -> LLVM.CodeGenFunction () (LLVM.Value out)) ->
   (inp -> out -> Bool) ->
   IO QC.Property
run qcgen codegen predicate = do
   funIO <- generateFunction derefTestCasePtr $ modul codegen
   return $ QC.forAll qcgen $ \x ->
      QCMon.monadicIO $ do
         y <-
            QCMon.run $
               EE.with x $ \xPtr ->
               EE.alloca $ \yPtr -> do
                  funIO xPtr yPtr
                  EE.peek yPtr
         QCMon.assert $ predicate x y


type Extract n a = QC.Gen (LLVM.Vector n a, Word32)

extractElem ::
   (TypeNum.Positive n,
    (n TypeNum.:*: LLVM.SizeOf a) ~ size, TypeNum.Natural size,
    Show a, Eq a,
    EE.MarshalVector a, EE.Marshal a, LLVM.IsSized a, LLVM.IsPrimitive a) =>
   Extract n a -> IO QC.Property
extractElem qcgen =
   run
      (fmap (uncurry LLVM.consStruct) qcgen)
      (\vi -> do
         v <- LLVM.extractvalue vi TypeNum.d0
         i <- LLVM.extractvalue vi TypeNum.d1
         LLVM.extractelement v i)
      (LLVM.uncurryStruct $ \v i a ->
         a == Fold.toList v !! fromIntegral i)


vectorSize :: LLVM.Vector n a -> Proxy n
vectorSize _ = Proxy

genVector :: (TypeNum.Positive n, QC.Arbitrary a) => Extract n a
genVector = do
   v <- QC.arbitrary
   i <- QC.choose (0, TypeNum.integralFromProxy (vectorSize v) - 1)
   return (v,i)


type Int2 = LLVM.IntN TypeNum.D2
type Int3 = LLVM.IntN TypeNum.D3
type Word2 = LLVM.WordN TypeNum.D2
type Word3 = LLVM.WordN TypeNum.D3
type Int24 = LLVM.IntN TypeNum.D24
type Word17 = LLVM.IntN TypeNum.D17


testsVector :: [(String, IO QC.Property)]
testsVector =
   map (mapFst ("Vector." ++)) $
   ("v8f32", extractElem (genVector :: Extract TypeNum.D8 Float)) :
   ("v5f64", extractElem (genVector :: Extract TypeNum.D5 Double)) :
   ("v7i1", extractElem (genVector :: Extract TypeNum.D7 Bool)) :
   ("v13i1", extractElem (genVector :: Extract TypeNum.D13 Bool)) :
   ("v4i2", extractElem (genVector :: Extract TypeNum.D4 Int2)) :
   ("v10i2", extractElem (genVector :: Extract TypeNum.D10 Word2)) :
   -- ToDo: broken on LLVM<=9: https://bugs.llvm.org/show_bug.cgi?id=44915
   ("v7i3", extractElem (genVector :: Extract TypeNum.D7 Int3)) :
   ("v5i3", extractElem (genVector :: Extract TypeNum.D5 Word3)) :
   ("v9i24", extractElem (genVector :: Extract TypeNum.D9 Int24)) :
   ("v3i17", extractElem (genVector :: Extract TypeNum.D3 Word17)) :
   ("v5i8", extractElem (genVector :: Extract TypeNum.D5 Word8)) :
   ("v3i16", extractElem (genVector :: Extract TypeNum.D3 Word16)) :
   ("v4i8", extractElem (genVector :: Extract TypeNum.D4 Int8)) :
   ("v7i32", extractElem (genVector :: Extract TypeNum.D7 Int32)) :
   []


{-
Conversion from a Ptr Word8 triggers improper optimization
if target data layout is not set for module prior to optimization.
-}
runViaBytePtr ::
   (Show inp, EE.Marshal inp, EE.Marshal out) =>
   QC.Gen inp ->
   (LLVM.Value inp -> LLVM.CodeGenFunction () (LLVM.Value out)) ->
   (inp -> out -> Bool) ->
   IO QC.Property
runViaBytePtr qcgen codegen predicate = do
   funIO <-
      generateFunction derefTestCasePtr $
         LLVM.createFunction LLVM.ExternalLinkage $ \xPtr yPtr -> do
            flip LLVM.store yPtr =<< codegen =<< LLVM.load =<< LLVM.bitcast xPtr
            LLVM.ret ()
   return $ QC.forAll qcgen $ \x ->
      QCMon.monadicIO $ do
         y <-
            QCMon.run $
               EE.with x $ \xPtr ->
               EE.alloca $ \yPtr -> do
                  funIO (castToBytePtr xPtr) yPtr
                  EE.peek yPtr
         QCMon.assert $ predicate x y

castToBytePtr :: LLVM.Ptr a -> LLVM.Ptr Word8
castToBytePtr = LLVM.fromPtr . castPtr . LLVM.uncheckedToPtr

extractValue ::
   (QC.Arbitrary s, Show s, EE.Marshal s, EE.Marshal a, Eq a) =>
   LP.Proxy s ->
   (s -> a) ->
   (forall r. LLVM.Value s -> LLVM.CodeGenFunction r (LLVM.Value a)) ->
   Bool ->
   IO QC.Property
extractValue LP.Proxy select extract viaBytePtr =
   (if viaBytePtr then runViaBytePtr else run)
      QC.arbitrary extract (\s x -> select s == x)

type Pair a b = LLVM.Struct (a,(b,()))
type Triple a b c = LLVM.Struct (a,(b,(c,())))

sfst :: LLVM.Struct (a,z) -> a
sfst (LLVM.Struct (a,_)) = a
ssnd :: LLVM.Struct (a,(b,z)) -> b
ssnd (LLVM.Struct (_,(b,_))) = b
sthd :: LLVM.Struct (a,(b,(c,z))) -> c
sthd (LLVM.Struct (_,(_,(c,_)))) = c

exv ::
   (LLVM.GetField s i, TypeNum.Natural i, LLVM.FieldType s i ~ a) =>
   Proxy i ->
   LLVM.Value (LLVM.Struct s) -> LLVM.CodeGenFunction r (LLVM.Value a)
exv = flip LLVM.extractvalue

proxyA :: LP.Proxy (Triple Int16 Bool Word64)
proxyA = LP.Proxy

proxyB :: LP.Proxy (Triple Bool Bool Int8)
proxyB = LP.Proxy

proxyC :: LP.Proxy (Pair Bool (Pair Float Word64))
proxyC = LP.Proxy

testsStruct :: [(String, Bool -> IO QC.Property)]
testsStruct =
   ("{i16,i1,i64} 0",
      extractValue proxyA sfst (exv TypeNum.d0)) :
   ("{i16,i1,i64} 1",
      extractValue proxyA ssnd (exv TypeNum.d1)) :
   ("{i16,i1,i64} 2",
      extractValue proxyA sthd (exv TypeNum.d2)) :
   ("{i1,i1,i8} 0",
      extractValue proxyB sfst (exv TypeNum.d0)) :
   ("{i1,i1,i8} 1",
      extractValue proxyB ssnd (exv TypeNum.d1)) :
   ("{i1,i1,i8} 2",
      extractValue proxyB sthd (exv TypeNum.d2)) :
   ("{i1,{float,i64}} 0",
      extractValue proxyC sfst (exv TypeNum.d0)) :
   ("{i1,{float,i64}} 1 0",
      extractValue proxyC (sfst.ssnd) (exv TypeNum.d0 <=< exv TypeNum.d1)) :
   ("{i1,{float,i64}} 1 1",
      extractValue proxyC (ssnd.ssnd) (exv TypeNum.d1 <=< exv TypeNum.d1)) :
   []


testsExtract :: [(String, IO QC.Property)]
testsExtract =
   map (mapFst ("Extract." ++)) $
      testsVector ++
      map (mapPair (("Struct." ++), ($False))) testsStruct ++
      map (mapPair (("StructByte." ++), ($True))) testsStruct