{-# LANGUAGE CPP, BangPatterns, MagicHash, ForeignFunctionInterface #-}
module Data.Vector.Compact.Blob
(
Blob(..)
, blobTag
, blobSizeInWords
, blobSizeInBytes
, blobSizeInBits
, blobFromWordList , blobFromWordListN
, blobToWordList
, blobFromByteArray
, blobToByteArray
, eqBlob
, head
, tail
, last
, consWord
, snocWord
, indexWord , indexByte
, extractSmallWord , extractSmallWord64
, extendToSize
, cutToSize
, forceToSize
, mapBlob
, shortZipWith
, longZipWith
, unsafeZipWith
, Hex(..)
, hexWord64 , hexWord64_
, peekBlob
, pokeBlob
, CFun10 , CFun20 , CFun11 , CFun21 , CFun11_ , CFun21_
, wrapCFun10 , wrapCFun20 , wrapCFun11 , wrapCFun21 , wrapCFun11_ , wrapCFun21_
)
where
import Prelude hiding ( head , tail , last )
import Data.Char
import Data.Bits
import Data.Int
import Data.Word
import qualified Data.List as L
import Control.Monad
import Control.Monad.ST
import GHC.Int
import GHC.Word
import GHC.Ptr
import GHC.Exts
import GHC.IO
import Foreign.C.Types
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal
import Foreign.Marshal.Array
import System.IO.Unsafe as Unsafe
import Control.Monad.Primitive
import Data.Primitive.ByteArray
data Blob
= Blob1 {-# UNPACK #-} !Word64
| Blob2 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
| Blob3 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
| Blob4 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
| Blob5 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
| Blob6 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64
| BlobN {-# UNPACK #-} !ByteArray
blobTag :: Blob -> Int
blobTag blob = I# (dataToTag# blob)
blobSizeInWords :: Blob -> Int
blobSizeInWords !blob = case blob of
BlobN !arr -> shiftR (sizeofByteArray arr) 3
otherwise -> blobTag blob + 1
blobSizeInBytes :: Blob -> Int
blobSizeInBytes !blob = case blob of
BlobN !arr -> sizeofByteArray arr
otherwise -> shiftL (blobTag blob + 1) 3
blobSizeInBits :: Blob -> Int
blobSizeInBits blob = shiftL (blobSizeInBytes blob) 3
blobFromWordList :: [Word64] -> Blob
blobFromWordList ws = blobFromWordListN (length ws) ws
blobFromWordListN :: Int -> [Word64] -> Blob
blobFromWordListN !n ws = case n of
0 -> Blob1 0
1 -> case ws of { (a:_) -> Blob1 a }
2 -> case ws of { (a:b:_) -> Blob2 a b }
3 -> case ws of { (a:b:c:_) -> Blob3 a b c }
4 -> case ws of { (a:b:c:d:_) -> Blob4 a b c d }
5 -> case ws of { (a:b:c:d:e:_) -> Blob5 a b c d e }
6 -> case ws of { (a:b:c:d:e:f:_) -> Blob6 a b c d e f }
_ -> BlobN (byteArrayFromListN n ws)
blobToWordList :: Blob -> [Word64]
blobToWordList blob = case blob of
Blob1 a -> a:[]
Blob2 a b -> a:b:[]
Blob3 a b c -> a:b:c:[]
Blob4 a b c d -> a:b:c:d:[]
Blob5 a b c d e -> a:b:c:d:e:[]
Blob6 a b c d e f -> a:b:c:d:e:f:[]
BlobN ba -> foldrByteArray (:) [] ba
blobFromByteArray :: ByteArray -> Blob
blobFromByteArray !ba
| nwords > 6 = if nwords1 == nwords
then BlobN ba
else BlobN (byteArrayFromListN nwords words )
| nwords == 0 = Blob1 0
| otherwise = blobFromWordListN nwords words
where
!nbytes = sizeofByteArray ba
!nwords1 = shiftR (nbytes ) 3
!nwords = shiftR (nbytes + 7) 3
words :: [Word64]
words = if nwords1 == nwords
then foldrByteArray (:) [] ba
else let !ofs = shiftL nwords1 3
!m = nbytes - ofs
w8_to_w64 :: Word8 -> Word64
w8_to_w64 = fromIntegral
!lastWord = L.foldl' (.|.) 0
[ shiftL (w8_to_w64 (indexByteArray ba (ofs + i))) (shiftL i 3)
| i<-[0..m-1]
]
in foldrByteArray (:) [lastWord] ba
blobToByteArray :: Blob -> ByteArray
blobToByteArray !blob = case blob of
BlobN ba -> ba
_ -> byteArrayFromListN (blobSizeInWords blob) (blobToWordList blob)
instance Show Blob where
showsPrec prec !blob
= showParen (prec > 10)
$ showString "blobFromWordList "
. shows (map Hex $ blobToWordList blob)
instance Eq Blob where
(==) = eqBlob
eqBlob :: Blob -> Blob -> Bool
eqBlob !x !y = if blobTag x /= blobTag y
then False
else case (x,y) of
( Blob1 a , Blob1 p ) -> a==p
( Blob2 a b , Blob2 p q ) -> a==p && b==q
( Blob3 a b c , Blob3 p q r ) -> a==p && b==q && c==r
( Blob4 a b c d , Blob4 p q r s ) -> a==p && b==q && c==r && d==s
( Blob5 a b c d e , Blob5 p q r s t ) -> a==p && b==q && c==r && d==s && e==t
( Blob6 a b c d e f , Blob6 p q r s t u ) -> a==p && b==q && c==r && d==s && e==t && f==u
( BlobN one , BlobN two ) -> one == two
_ -> error "FATAL ERROR: should not happen"
newtype Hex
= Hex Word64
instance Show Hex where
show (Hex w) = hexWord64 w
hexWord64 :: Word64 -> String
hexWord64 word= '0' : 'x' : hexWord64_ word
hexWord64_ :: Word64 -> String
hexWord64_ word = go [] 16 word where
go !acc 0 !w = acc
go !acc !k !w = go (hexNibble (w .&. 15) : acc) (k-1) (shiftR w 4)
hexNibble :: Integral a => a -> Char
hexNibble i0 = let i = (fromIntegral i0 :: Int) in if (i < 10) then chr (i+48) else chr (i+87)
indexWord :: Blob -> Int -> Word64
indexWord !blob !idx = case blob of
Blob1 a
| idx == 0 -> a
| otherwise -> error "Blob/indexWord: index out of bounds"
Blob2 a b
| idx == 0 -> a
| idx == 1 -> b
| otherwise -> error "Blob/indexWord: index out of bounds"
Blob3 a b c
| idx == 0 -> a
| idx == 1 -> b
| idx == 2 -> c
| otherwise -> error "Blob/indexWord: index out of bounds"
Blob4 a b c d
| idx == 0 -> a
| idx == 1 -> b
| idx == 2 -> c
| idx == 3 -> d
| otherwise -> error "Blob/indexWord: index out of bounds"
Blob5 a b c d e
| idx == 0 -> a
| idx == 1 -> b
| idx == 2 -> c
| idx == 3 -> d
| idx == 4 -> e
| otherwise -> error "Blob/indexWord: index out of bounds"
Blob6 a b c d e f
| idx == 0 -> a
| idx == 1 -> b
| idx == 2 -> c
| idx == 3 -> d
| idx == 4 -> e
| idx == 5 -> f
| otherwise -> error "Blob/indexWord: index out of bounds"
BlobN arr -> indexByteArray arr idx
indexByte :: Blob -> Int -> Word8
indexByte !blob !idx =
let !w = indexWord blob (shiftR idx 3)
in fromIntegral $ shiftR w (8 * (idx .&. 7))
head :: Blob -> Word64
head blob = case blob of
Blob1 a -> a
Blob2 a _ -> a
Blob3 a _ _ -> a
Blob4 a _ _ _ -> a
Blob5 a _ _ _ _ -> a
Blob6 a _ _ _ _ _ -> a
BlobN arr -> indexByteArray arr 0
last :: Blob -> Word64
last blob = case blob of
Blob1 z -> z
Blob2 _ z -> z
Blob3 _ _ z -> z
Blob4 _ _ _ z -> z
Blob5 _ _ _ _ z -> z
Blob6 _ _ _ _ _ z -> z
BlobN arr -> indexByteArray arr (blobSizeInWords blob - 1)
consWord :: Word64 -> Blob -> Blob
consWord !y !blob = case blob of
Blob1 a -> Blob2 y a
Blob2 a b -> Blob3 y a b
Blob3 a b c -> Blob4 y a b c
Blob4 a b c d -> Blob5 y a b c d
Blob5 a b c d e -> Blob6 y a b c d e
_ -> wrapCFun11_ (c_cons y) (+1) blob
snocWord :: Blob -> Word64 -> Blob
snocWord !blob !z = case blob of
Blob1 a -> Blob2 a z
Blob2 a b -> Blob3 a b z
Blob3 a b c -> Blob4 a b c z
Blob4 a b c d -> Blob5 a b c d z
Blob5 a b c d e -> Blob6 a b c d e z
_ -> wrapCFun11_ (c_snoc z) (+1) blob
tail :: Blob -> Blob
tail !blob = case blob of
Blob1 _ -> Blob1 0
Blob2 _ b -> Blob1 b
Blob3 _ b c -> Blob2 b c
Blob4 _ b c d -> Blob3 b c d
Blob5 _ b c d e -> Blob4 b c d e
Blob6 _ b c d e f -> Blob5 b c d e f
_ -> wrapCFun11_ c_tail id blob
extractSmallWord :: Integral a => Int -> Blob -> Int -> a
extractSmallWord !n !blob !ofs = fromIntegral (extractSmallWord64 n blob ofs)
extractSmallWord64 :: Int -> Blob -> Int -> Word64
extractSmallWord64 !n !blob !ofs
| q2 == q1 = mask .&. shiftR (indexWord blob q1) r1
| q2 == q1 + 1 = mask .&. (shiftR (indexWord blob q1) r1 .|. shiftL (indexWord blob q2) (64-r1))
| otherwise = error "Blob/extractSmallWord: FATAL ERROR"
where
!mask = shiftL 1 n - 1
!end = ofs + n - 1
!q1 = shiftR ofs 6
!q2 = shiftR end 6
!r1 = ofs .&. 63
pokeBlob :: Ptr Word64 -> Blob -> IO Int
pokeBlob !ptr !blob = case blob of
Blob1 a -> poke ptr a >> return 1
Blob2 a b -> pokeArray ptr [a,b] >> return 2
Blob3 a b c -> pokeArray ptr [a,b,c] >> return 3
Blob4 a b c d -> pokeArray ptr [a,b,c,d] >> return 4
Blob5 a b c d e -> pokeArray ptr [a,b,c,d,e] >> return 5
Blob6 a b c d e f -> pokeArray ptr [a,b,c,d,e,f] >> return 6
BlobN ba -> let !nbytes = sizeofByteArray ba
in copyByteArrayToPtr ba 0 ptr nbytes >> return (shiftR nbytes 3)
peekBlob :: Int -> Ptr Word64 -> IO Blob
peekBlob !n !ptr =
case n of
0 -> return (Blob1 0)
1 -> peek ptr >>= \a -> return (Blob1 a)
2 -> peekArray 2 ptr >>= \[a,b] -> return (Blob2 a b)
3 -> peekArray 3 ptr >>= \[a,b,c] -> return (Blob3 a b c)
4 -> peekArray 4 ptr >>= \[a,b,c,d] -> return (Blob4 a b c d)
5 -> peekArray 5 ptr >>= \[a,b,c,d,e] -> return (Blob5 a b c d e)
6 -> peekArray 6 ptr >>= \[a,b,c,d,e,f] -> return (Blob6 a b c d e f)
_ -> do
mut <- newByteArray (shiftL n 3)
copyPtrToByteArray ptr mut 0 (shiftL n 3)
ba <- unsafeFreezeByteArray mut
return (BlobN ba)
type CFun10 a = CInt -> Ptr Word64 -> IO a
type CFun20 a = CInt -> Ptr Word64 -> CInt -> Ptr Word64 -> IO a
type CFun11 a = CInt -> Ptr Word64 -> Ptr CInt -> Ptr Word64 -> IO a
type CFun21 a = CInt -> Ptr Word64 ->
CInt -> Ptr Word64 -> Ptr CInt -> Ptr Word64 -> IO a
type CFun11_ = CFun11 ()
type CFun21_ = CFun21 ()
wrapCFun10_IO :: CFun10 a -> Blob -> IO a
wrapCFun10_IO action blob = do
let !n = blobSizeInWords blob
allocaArray n $ \ptr1 -> do
pokeBlob ptr1 blob
action (fromIntegral n) ptr1
wrapCFun20_IO :: CFun20 a -> Blob -> Blob -> IO a
wrapCFun20_IO action blob1 blob2 = do
let !n1 = blobSizeInWords blob1
let !n2 = blobSizeInWords blob2
allocaArray n1 $ \ptr1 -> do
pokeBlob ptr1 blob1
allocaArray n2 $ \ptr2 -> do
pokeBlob ptr2 blob2
action (fromIntegral n1) ptr1 (fromIntegral n2) ptr2
wrapCFun11_IO :: CFun11 a -> Int -> Blob -> IO (a,Blob)
wrapCFun11_IO action m blob = do
let !n = blobSizeInWords blob
allocaArray n $ \ptr1 -> do
pokeBlob ptr1 blob
allocaArray m $ \ptr2 -> do
alloca $ \q -> do
y <- action (fromIntegral n) ptr1 q ptr2
k <- peek q
new <- peekBlob (fromIntegral k) ptr2
return (y,new)
wrapCFun21_IO :: CFun21 a -> Int -> Blob -> Blob -> IO (a,Blob)
wrapCFun21_IO action m blob1 blob2 = do
let !n1 = blobSizeInWords blob1
allocaArray n1 $ \ptr1 -> do
pokeBlob ptr1 blob1
let !n2 = blobSizeInWords blob2
allocaArray n2 $ \ptr2 -> do
pokeBlob ptr2 blob2
allocaArray m $ \ptr3 -> do
alloca $ \q -> do
y <- action (fromIntegral n1) ptr1 (fromIntegral n2) ptr2 q ptr3
k <- peek q
new <- peekBlob (fromIntegral k) ptr3
return (y,new)
{-# NOINLINE wrapCFun10 #-}
wrapCFun10 :: CFun10 a -> Blob -> a
wrapCFun10 action blob = Unsafe.unsafePerformIO $ wrapCFun10_IO action blob
{-# NOINLINE wrapCFun20 #-}
wrapCFun20 :: CFun20 a -> Blob -> Blob -> a
wrapCFun20 action blob1 blob2 = Unsafe.unsafePerformIO $ wrapCFun20_IO action blob1 blob2
{-# NOINLINE wrapCFun11 #-}
wrapCFun11 :: CFun11 a -> (Int -> Int) -> Blob -> (a,Blob)
wrapCFun11 action f blob = Unsafe.unsafePerformIO $ do
let !n = blobSizeInWords blob
wrapCFun11_IO action (f n) blob
{-# NOINLINE wrapCFun11_ #-}
wrapCFun11_ :: CFun11_ -> (Int -> Int) -> Blob -> Blob
wrapCFun11_ action f blob = Unsafe.unsafePerformIO $ do
let !n = blobSizeInWords blob
snd <$> wrapCFun11_IO action (f n) blob
{-# NOINLINE wrapCFun21 #-}
wrapCFun21 :: CFun21 a -> (Int -> Int -> Int) -> Blob -> Blob -> (a,Blob)
wrapCFun21 action f blob1 blob2 = Unsafe.unsafePerformIO $ do
let !n1 = blobSizeInWords blob1
let !n2 = blobSizeInWords blob2
wrapCFun21_IO action (f n1 n2) blob1 blob2
{-# NOINLINE wrapCFun21_ #-}
wrapCFun21_ :: CFun21_ -> (Int -> Int -> Int) -> Blob -> Blob -> Blob
wrapCFun21_ action f blob1 blob2 = Unsafe.unsafePerformIO $ do
let !n1 = blobSizeInWords blob1
let !n2 = blobSizeInWords blob2
snd <$> wrapCFun21_IO action (f n1 n2) blob1 blob2
foreign import ccall unsafe "identity" c_identity :: CFun11_
foreign import ccall unsafe "tail" c_tail :: CFun11_
foreign import ccall unsafe "cons" c_cons :: Word64 -> CFun11_
foreign import ccall unsafe "snoc" c_snoc :: Word64 -> CFun11_
foreign import ccall unsafe "rotate_left" c_rotate_left :: CInt -> CFun11_
foreign import ccall unsafe "rotate_right" c_rotate_right :: CInt -> CFun11_
foreign import ccall unsafe "shift_left_strict" c_shift_left_strict :: CInt -> CFun11_
foreign import ccall unsafe "shift_left_nonstrict" c_shift_left_nonstrict :: CInt -> CFun11_
foreign import ccall unsafe "shift_right" c_shift_right :: CInt -> CFun11_
extendToSize :: Int -> Blob -> Blob
extendToSize tgt blob
| n >= tgt = blob
| otherwise = blobFromWordListN tgt (blobToWordList blob ++ replicate (tgt-n) 0)
where
n = blobSizeInWords blob
cutToSize :: Int -> Blob -> Blob
cutToSize tgt blob
| n <= tgt = blob
| otherwise = blobFromWordListN tgt (take tgt $ blobToWordList blob)
where
n = blobSizeInWords blob
forceToSize :: Int -> Blob -> Blob
forceToSize tgt blob
| n == tgt = blob
| n >= tgt = blobFromWordListN tgt (take tgt $ blobToWordList blob)
| otherwise = blobFromWordListN tgt (blobToWordList blob ++ replicate (tgt-n) 0)
where
n = blobSizeInWords blob
mapBlob :: (Word64 -> Word64) -> Blob -> Blob
mapBlob f !blob = case blob of
Blob1 a -> Blob1 (f a)
Blob2 a b -> Blob2 (f a) (f b)
Blob3 a b c -> Blob3 (f a) (f b) (f c)
Blob4 a b c d -> Blob4 (f a) (f b) (f c) (f d)
Blob5 a b c d e -> Blob5 (f a) (f b) (f c) (f d) (f e)
Blob6 a b c d e y -> Blob6 (f a) (f b) (f c) (f d) (f e) (f y)
BlobN ba -> runST $ do
let !n = blobSizeInWords blob
mut <- newByteArray (shiftL n 3)
forM_ [0..n-1] $ \i -> writeByteArray mut i $ f (indexByteArray ba i)
new <- unsafeFreezeByteArray mut
return (BlobN new)
shortZipWith :: (Word64 -> Word64 -> Word64) -> Blob -> Blob -> Blob
shortZipWith f !blob1 !blob2
| n1 == n2 = unsafeZipWith f blob1 blob2
| n1 > n2 = unsafeZipWith f (cutToSize n2 blob1) blob2
| otherwise = unsafeZipWith f blob1 (cutToSize n1 blob2)
where
n1 = blobSizeInWords blob1
n2 = blobSizeInWords blob2
longZipWith :: (Word64 -> Word64 -> Word64) -> Blob -> Blob -> Blob
longZipWith f !blob1 !blob2
| n1 == n2 = unsafeZipWith f blob1 blob2
| n1 < n2 = unsafeZipWith f (extendToSize n2 blob1) blob2
| otherwise = unsafeZipWith f blob1 (extendToSize n1 blob2)
where
n1 = blobSizeInWords blob1
n2 = blobSizeInWords blob2
unsafeZipWith :: (Word64 -> Word64 -> Word64) -> Blob -> Blob -> Blob
unsafeZipWith f !blob1 !blob2 = case (blob1,blob2) of
( Blob1 a , Blob1 p ) -> Blob1 (f a p)
( Blob2 a b , Blob2 p q ) -> Blob2 (f a p) (f b q)
( Blob3 a b c , Blob3 p q r ) -> Blob3 (f a p) (f b q) (f c r)
( Blob4 a b c d , Blob4 p q r s ) -> Blob4 (f a p) (f b q) (f c r) (f d s)
( Blob5 a b c d e , Blob5 p q r s t ) -> Blob5 (f a p) (f b q) (f c r) (f d s) (f e t)
( Blob6 a b c d e y , Blob6 p q r s t u ) -> Blob6 (f a p) (f b q) (f c r) (f d s) (f e t) (f y u)
( BlobN ba1 , BlobN ba2 ) ->
runST $ do
let !n = blobSizeInWords blob1
mut <- newByteArray (shiftL n 3)
forM_ [0..n-1] $ \i -> writeByteArray mut i $ f (indexByteArray ba1 i) (indexByteArray ba2 i)
new <- unsafeFreezeByteArray mut
return (BlobN new)
_ -> error "FATAL ERROR: should not happen"
instance Bits Blob where
(.&.) = shortZipWith (.&.)
(.|.) = longZipWith (.|.)
xor = longZipWith xor
complement = mapBlob complement
shiftL blob k = wrapCFun11_ (c_shift_left_nonstrict (fromIntegral k)) f blob where f n = n + shiftR (k+63) 6
shiftR blob k = wrapCFun11_ (c_shift_right (fromIntegral k)) id blob
rotateL blob k = wrapCFun11_ (c_rotate_left (fromIntegral k)) id blob
rotateR blob k = wrapCFun11_ (c_rotate_right (fromIntegral k)) id blob
#if MIN_VERSION_base(4,12,0)
bitSizeMaybe = Just . blobSizeInBits
bitSize = blobSizeInBits
#else
bitSize = blobSizeInBits
#endif
zeroBits = Blob1 0
isSigned _ = False
popCount blob = L.foldl' (+) 0 (map popCount $ blobToWordList blob)
testBit !blob !k = if q >= n then False else testBit (indexWord blob q) r where
(q,r) = divMod k 64
n = blobSizeInWords blob
bit k = blobFromWordListN (q+1) (replicate q 0 ++ [bit r]) where
(q,r) = divMod k 64
#if MIN_VERSION_base(4,12,0)
instance FiniteBits Blob where
finiteBitSize = blobSizeInBits
#endif
baToList :: ByteArray -> [Word64]
baToList = foldrByteArray (:) []
baSizeInWords :: ByteArray -> Int
baSizeInWords ba = shiftR (sizeofByteArray ba) 3
copyByteArrayToPtr :: ByteArray -> Int -> Ptr a -> Int -> IO ()
copyByteArrayToPtr (ByteArray ba#) (I# ofs) (Ptr p) (I# n) = primitive_ $ copyByteArrayToAddr# ba# ofs p n
copyPtrToByteArray :: Ptr a -> MutableByteArray (PrimState IO) -> Int -> Int -> IO ()
copyPtrToByteArray (Ptr p) (MutableByteArray mut#) (I# ofs) (I# n) = primitive_ $ copyAddrToByteArray# p mut# ofs n