bloomfilter-blocked-0.1.0.0: src/Data/BloomFilter/Classic/BitArray.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
-- | Minimal bit array implementation.
module Data.BloomFilter.Classic.BitArray (
BitArray (..),
unsafeIndex,
prefetchIndex,
MBitArray (..),
new,
unsafeSet,
unsafeRead,
freeze,
unsafeFreeze,
thaw,
serialise,
deserialise,
) where
import Control.Exception (assert)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Control.Monad.ST (ST)
import Data.Bits
import Data.Primitive.ByteArray
import Data.Primitive.PrimArray
import Data.Word (Word64, Word8)
import GHC.Exts (Int (I#), prefetchByteArray0#)
import GHC.ST (ST (ST))
-- | Bit vector backed up by an array of Word64
--
-- This vector's offset and length are multiples of 64
newtype BitArray = BitArray (PrimArray Word64)
deriving stock (Eq, Show)
{-# INLINE unsafeIndex #-}
unsafeIndex :: BitArray -> Int -> Bool
unsafeIndex (BitArray arr) !i =
assert (j >= 0 && j < sizeofPrimArray arr) $
unsafeTestBit (indexPrimArray arr j) k
where
!j = unsafeShiftR i 6 -- `div` 64, bit index to Word64 index.
!k = i .&. 63 -- `mod` 64, bit within Word64
{-# INLINE prefetchIndex #-}
prefetchIndex :: BitArray -> Int -> ST s ()
prefetchIndex (BitArray (PrimArray ba#)) !i =
let !(I# bi#) = i `unsafeShiftR` 3 in
ST (\s -> case prefetchByteArray0# ba# bi# s of
s' -> (# s', () #))
-- We only need to shiftR 3 here, not 6, because we're going from a bit
-- offset to a byte offset for prefetch. Whereas in unsafeIndex, we go from
-- a bit offset to a Word64 offset, so an extra shiftR 3, for 6 total.
newtype MBitArray s = MBitArray (MutablePrimArray s Word64)
-- | Will create an explicitly pinned byte array.
-- This is done because pinned byte arrays allow for more efficient
-- serialisation, but the definition of 'isByteArrayPinned' changed in GHC 9.6,
-- see <https://gitlab.haskell.org/ghc/ghc/-/issues/22255>.
--
-- TODO: remove this workaround once a solution exists, e.g. a new primop that
-- allows checking for implicit pinning.
new :: Int -> ST s (MBitArray s)
new s = do
mba@(MutableByteArray mba#) <- newPinnedByteArray numBytes
setByteArray mba 0 numBytes (0 :: Word8)
pure (MBitArray (MutablePrimArray mba#))
where
!numWords = roundUpTo64 s
!numBytes = unsafeShiftL numWords 3 -- * 8
-- this may overflow, but so be it (2^64 bits is a lot)
roundUpTo64 :: Int -> Int
roundUpTo64 i = unsafeShiftR (i + 63) 6 -- `div` 64, rounded up
serialise :: BitArray -> (ByteArray, Int, Int)
serialise bitArray =
let ba = asByteArray bitArray
in (ba, 0, sizeofByteArray ba)
where
asByteArray (BitArray (PrimArray ba#)) = ByteArray ba#
{-# INLINE deserialise #-}
-- | Do an inplace overwrite of the byte array representing the bit block.
deserialise :: PrimMonad m
=> MBitArray (PrimState m)
-> (MutableByteArray (PrimState m) -> Int -> Int -> m ())
-> m ()
deserialise bitArray fill = do
let mba = asMutableByteArray bitArray
len <- getSizeofMutableByteArray mba
fill mba 0 len
where
asMutableByteArray (MBitArray (MutablePrimArray mba#)) =
MutableByteArray mba#
unsafeSet :: MBitArray s -> Int -> ST s ()
unsafeSet (MBitArray arr) i = do
#ifdef NO_IGNORE_ASSERTS
sz <- getSizeofMutablePrimArray arr
assert (j >= 0 && j < sz) $ pure ()
#endif
w <- readPrimArray arr j
writePrimArray arr j (unsafeSetBit w k)
where
!j = unsafeShiftR i 6 -- `div` 64
!k = i .&. 63 -- `mod` 64
unsafeRead :: MBitArray s -> Int -> ST s Bool
unsafeRead (MBitArray arr) i = do
#ifdef NO_IGNORE_ASSERTS
sz <- getSizeofMutablePrimArray arr
assert (j >= 0 && j < sz) $ pure ()
#endif
w <- readPrimArray arr j
pure $! unsafeTestBit w k
where
!j = unsafeShiftR i 6 -- `div` 64
!k = i .&. 63 -- `mod` 64
freeze :: MBitArray s -> ST s BitArray
freeze (MBitArray arr) = do
len <- getSizeofMutablePrimArray arr
BitArray <$> freezePrimArray arr 0 len
unsafeFreeze :: MBitArray s -> ST s BitArray
unsafeFreeze (MBitArray arr) =
BitArray <$> unsafeFreezePrimArray arr
thaw :: BitArray -> ST s (MBitArray s)
thaw (BitArray arr) =
MBitArray <$> thawPrimArray arr 0 (sizeofPrimArray arr)
{-# INLINE unsafeTestBit #-}
-- like testBit but using unsafeShiftL instead of shiftL
unsafeTestBit :: Word64 -> Int -> Bool
unsafeTestBit w k = w .&. (1 `unsafeShiftL` k) /= 0
{-# INLINE unsafeSetBit #-}
-- like setBit but using unsafeShiftL instead of shiftL
unsafeSetBit :: Word64 -> Int -> Word64
unsafeSetBit w k = w .|. (1 `unsafeShiftL` k)