inline-asm-0.5.0.0: test/Spec.hs
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
{-# LANGUAGE GHCForeignImportPrim, UnliftedFFITypes, UnboxedTuples #-}
{-# LANGUAGE ViewPatterns #-}
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.ByteString(ByteString)
import Foreign.Ptr
import GHC.Word
import Test.Hspec
import Test.Hspec.Core.QuickCheck
import Test.QuickCheck
import Language.Asm.Inline
import Language.Asm.Inline.QQ
import Language.Asm.Inline.Util
defineAsmFun "timesTwoInt" [t| Int -> Int |] "add %rbx, %rbx"
defineAsmFun "plusInt" [t| Int -> Int -> Int |] "add %r14, %rbx"
defineAsmFun "swapInts" [t| Int -> Int -> (Int, Int) |] "xchg %rbx, %r14"
defineAsmFun "noInputs"
[asmTy| (_ : Unit) | (out : Int) |]
[asm|
mov $42, {out}
|]
defineAsmFunM "rdtsc"
[asmTy| | (out : Word64) |]
[asm|
rdtsc
mov %rdx, {out}
shl $32, {out}
add %rax, {out}
|]
defineAsmFun "timesTwoIntQQ"
[asmTy| (a : Int) | (_ : Int) |]
[asm| add {a}, {a} |]
defineAsmFun "plusIntQQ"
[asmTy| (a : Int) (b : Int) | (_ : Int) |]
[asm| add {b}, {a} |]
defineAsmFun "plus3IntQQ"
[asmTy| (a : Int) (b : Int) (c : Int) | (_ : Int) |]
[asm|
add {b}, {a}
add {c}, {a}
|]
defineAsmFun "swap2p1QQ"
[asmTy| (a : Int) (b : Int) | (a : Int) (b : Int) |]
[asm|
xchg {a}, {b}
add $1, {b}
|]
defineAsmFun "timesTwoFloatQQ"
[asmTy| (a : Float) | (_ : Float) |]
[asm| addss {a}, {a} |]
defineAsmFun "plusFloatQQ"
[asmTy| (a : Float) (b : Float) | (_ : Float) |]
[asm| addss {b}, {a} |]
defineAsmFun "timesTwoDoubleQQ"
[asmTy| (a : Double) | (_ : Double) |]
[asm| addsd {a}, {a} |]
defineAsmFun "plusDoubleQQ"
[asmTy| (a : Double) (b : Double) | (_ : Double) |]
[asm| addsd {b}, {a} |]
defineAsmFun "timesTwoEverything"
[asmTy| (d : Double) (n : Int) (f : Float) (w : Word) | (_ : Double) (_ : Int) (_ : Float) (_ : Word) |]
[asm|
addsd {d}, {d}
addss {f}, {f}
add {n}, {n}
add {w}, {w}
|]
defineAsmFun "addPtr"
[asmTy| (ptr : Ptr Int) (shift : Int) | (_ : Ptr Int) |]
[asm|
add {shift}, {ptr}
|]
defineAsmFun "swapPtrs"
[asmTy| (a : Ptr Int) (b : Ptr Int) | (_ : Ptr Int) (_ : Ptr Int) |]
[asm|
xchg {a}, {b}
|]
defineAsmFun "lastChar"
[asmTy| (bs : ByteString) (def : Word) | (w : Word) |]
[asm|
test {bs:len}, {bs:len}
jz is_zero
movzbq -1({bs:ptr},{bs:len}), {w}
RET_HASK
is_zero:
mov {def}, {w}
|]
defineAsmFun "countCharsSSE42"
[asmTy| (ch : Word8) (ptr : Ptr Word8) (len : Int) | (cnt : Int) |] $
unroll "i" [12..15]
[asm|
push %r{i}|] <> [asm|
vmovd {ch}, %xmm15
vpxor %xmm0, %xmm0, %xmm0
vpshufb %xmm0, %xmm15, %xmm15
shr $7, {len}
mov $16, %eax
mov $16, %edx
xor {cnt}, {cnt}
{move ptr rdi}
loop: |] <> unrolls "i" [1..8] [
[asm|
vmovdqa {(i - 1) * 0x10}({ptr}), %xmm{i}
|], [asm|
vpcmpestrm $10, %xmm15, %xmm{i}
vmovdqa %xmm0, %xmm{i}
|], [asm|
vmovq %xmm{i}, %r{i + 7}
|], [asm|
popcnt %r{i + 7}, %r{i + 7}
|], [asm|
add %r{i + 7}, {cnt}
|]
] <>
[asm|
add $128, {ptr}
dec {len}
jnz loop|] <> unroll "i" [15,14..12] [asm|
pop %r{i} |]
countChars :: Word8 -> BS.ByteString -> Int
countChars ch bs | BS.length bs <= 256 = BS.count ch bs
| otherwise = BS.count ch (substr 0 startLen bs)
+ countCharsSSE42 ch (castPtr alignedPtr) alignedLen
+ BS.count ch (substr endPos endLen bs)
where
basePtr = getBSAddr bs
alignedPtr = alignPtr basePtr alignment
startLen = alignedPtr `minusPtr` basePtr
(alignedLen, endLen) = let remLen = BS.length bs - startLen
remainder = remLen `rem` alignment
in (remLen - remainder, remainder)
endPos = startLen + alignedLen
alignment = 128
asBS :: ASCIIString -> BS.ByteString
asBS (ASCIIString str) = BS8.pack str
main :: IO ()
main = hspec $ modifyMaxSuccess (const 1000) $ do
describe "Works on units" $ do
it "no inputs is ok" $ noInputs Unit `shouldBe` 42
describe "Works on rdtsc" $ do
it "is increasing" $ property $ \() -> do
v1 <- rdtsc
v2 <- rdtsc
(v1, v2) `shouldSatisfy` uncurry (<)
describe "Works with Ints (the non-QQ version)" $ do
it "timesTwo" $ property $ \num -> timesTwoInt num `shouldBe` num * 2
it "plusInt" $ property $ \n1 n2 -> plusInt n1 n2 `shouldBe` n1 + n2
it "swap returns a tuple properly" $ property $ \n1 n2 -> swapInts n1 n2 `shouldBe` (n2, n1)
describe "Works on Ints" $ do
it "timesTwo" $ property $ \num -> timesTwoIntQQ num `shouldBe` num * 2
it "plusInt" $ property $ \n1 n2 -> plusIntQQ n1 n2 `shouldBe` n1 + n2
it "plus3Int" $ property $ \n1 n2 n3 -> plus3IntQQ n1 n2 n3 `shouldBe` n1 + n2 + n3
it "swap returns a tuple properly" $ property $ \n1 n2 -> swap2p1QQ n1 n2 `shouldBe` (n2, n1 + 1)
describe "Works on Floats" $ do
it "timesTwo" $ property $ \num -> timesTwoFloatQQ num `shouldBe` num * 2
it "plusFloat" $ property $ \n1 n2 -> plusFloatQQ n1 n2 `shouldBe` n1 + n2
describe "Works on Doubles" $ do
it "timesTwo" $ property $ \num -> timesTwoDoubleQQ num `shouldBe` num * 2
it "plusDouble" $ property $ \n1 n2 -> plusDoubleQQ n1 n2 `shouldBe` n1 + n2
describe "Works on Ptrs" $ do
it "addPtr" $ property $ \int num -> let ptr = intToPtr int
in addPtr ptr num `shouldBe` (ptr `plusPtr` num)
it "swap returns a tuple properly" $ property $ \n1 n2 -> let p1 = intToPtr n1
p2 = intToPtr n2
in swapPtrs p1 p2 `shouldBe` (p2, p1)
describe "Works on mixed types" $
it "timesTwoEverything" $ property $ \d n f w -> timesTwoEverything d n f w `shouldBe` (d + d, n * 2, f + f, w * 2)
describe "Works on ByteString" $
it "lastChar" $ property $ \(asBS -> bs) def -> lastChar bs def `shouldBe` if BS.null bs
then def
else fromIntegral $ fromEnum $ BS.last bs
describe "More examples" $
it "counting chars" $ property $ \(InfiniteList infList _) len needle ->
let bs = BS.pack $ take (len * 100) infList
in countChars needle bs `shouldBe` BS.count needle bs
intToPtr :: Int -> Ptr a
intToPtr = intPtrToPtr . IntPtr
substr :: Int -> Int -> BS.ByteString -> BS.ByteString
substr start len = BS.take len . BS.drop start