finitary-derive-3.0.0.1: src/Data/Finitary/PackBits.hs
{-
- Copyright (C) 2019 Koz Ross <koz.ross@retro-freedom.nz>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Extra.Solver #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE ViewPatterns #-}
#ifndef BITVEC_UNSAFE
-- |
-- Module: Data.Finitary.PackBits
-- Description: Scheme for bit-packing @Finitary@ types.
-- Copyright: (C) Koz Ross 2019
-- License: GPL version 3.0 or later
-- Stability: Experimental
-- Portability: GHC only
--
-- From the [Kraft-McMillan
-- inequality](https://en.wikipedia.org/wiki/Kraft%E2%80%93McMillan_inequality)
-- and
-- the fact that we are not able to have \'fractional\' bits, we can derive a
-- fixed-length code into a bitstring for any 'Finitary' type @a@, with code
-- length \(\lceil \log_{2}(\texttt{Cardinality a}) \rceil\) bits. This code is
-- essentially a binary representation of the index of each inhabitant of @a@.
-- On that basis, we can derive an 'VU.Unbox' instance, representing
-- the entire 'VU.Vector' as an unboxed [bit
-- array](https://en.wikipedia.org/wiki/Bit_array).
--
-- This encoding is advantageous from the point of view of space - there is no
-- tighter possible packing that preserves \(\Theta(1)\) random access and also
-- allows the full range of 'VU.Vector' operations. If you are concerned about
-- space usage above all, this is the best choice for you.
--
-- Because access to individual bits is slower than whole bytes or words, this
-- encoding adds some overhead. Additionally, a primary advantage of bit arrays
-- (the ability to perform \'bulk\' operations on bits efficiently) is not made
-- use of here. Therefore, if speed matters more than compactness, this encoding
-- is suboptimal.
--
-- This encoding is __thread-safe__, and thus slightly slower. If you are certain
-- that race conditions cannot occur for your code, you can gain a speed improvement
-- by using "Data.Finitary.PackBits.Unsafe" instead.
module Data.Finitary.PackBits
#else
-- |
-- Module: Data.Finitary.PackBits.Unsafe
-- Description: Scheme for bit-packing @Finitary@ types.
-- Copyright: (C) Koz Ross 2019
-- License: GPL version 3.0 or later
-- Stability: Experimental
-- Portability: GHC only
--
-- From the [Kraft-McMillan
-- inequality](https://en.wikipedia.org/wiki/Kraft%E2%80%93McMillan_inequality)
-- and
-- the fact that we are not able to have \'fractional\' bits, we can derive a
-- fixed-length code into a bitstring for any 'Finitary' type @a@, with code
-- length \(\lceil \log_{2}(\texttt{Cardinality a}) \rceil\) bits. This code is
-- essentially a binary representation of the index of each inhabitant of @a@.
-- On that basis, we can derive an 'VU.Unbox' instance, representing
-- the entire 'VU.Vector' as an unboxed [bit
-- array](https://en.wikipedia.org/wiki/Bit_array).
--
-- This encoding is advantageous from the point of view of space - there is no
-- tighter possible packing that preserves \(\Theta(1)\) random access and also
-- allows the full range of 'VU.Vector' operations. If you are concerned about
-- space usage above all, this is the best choice for you.
--
-- Because access to individual bits is slower than whole bytes or words, this
-- encoding adds some overhead. Additionally, a primary advantage of bit arrays
-- (the ability to perform \'bulk\' operations on bits efficiently) is not made
-- use of here. Therefore, if speed matters more than compactness, this encoding
-- is suboptimal.
--
-- This encoding is __not__ thread-safe, in exchange for performance. If you
-- suspect race conditions are possible, it's better to use
-- "Data.Finitary.PackBits" instead.
module Data.Finitary.PackBits.Unsafe
#endif
( -- * Packing and unpacking between a type and a bit vector
PackBits(PackedBits, Packed)
, BulkPack, exposeVector
-- * Helpers
, intoBits, outOfBits
)
where
-- base
import Data.Kind (Type)
import Data.Hashable (Hashable(..))
import GHC.Exts
import GHC.TypeNats
-- binary
import qualified Data.Binary as Bin
-- bitvec
#ifndef BITVEC_UNSAFE
import qualified Data.Bit.ThreadSafe as BV
#else
import qualified Data.Bit as BV
#endif
-- coercible-utils
import CoercibleUtils (op, over, over2)
-- deepseq
import Control.DeepSeq (NFData(..))
-- finitary
import Data.Finitary (Finitary(..))
-- finitary-derive
import Data.Finitary.PackWords
( PackWords(PackedWords), intoWords, outOfWords )
-- finite-typelits
import Data.Finite.Internal (Finite(..))
-- ghc-typelits-extra
import GHC.TypeLits.Extra
-- primitive
import Data.Primitive.ByteArray (ByteArray(..))
-- vector
import qualified Data.Vector.Unboxed.Base as VU
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Generic.Mutable as VGM
-- vector-binary-instances
import Data.Vector.Binary ()
-- vector-instances
import Data.Vector.Instances ()
--------------------------------------------------------------------------------
-- | An opaque wrapper around @a@, representing each value as a 'bit-packed'
-- encoding.
newtype PackBits (a :: Type) = PackedBits (VU.Vector BV.Bit)
deriving (Eq, Show)
type role PackBits nominal
-- | To provide (something that resembles a) data constructor for 'PackBits', we
-- provide the following pattern. It can be used like any other data
-- constructor:
--
-- > import Data.Finitary.PackBits
-- >
-- > anInt :: PackBits Int
-- > anInt = Packed 10
-- >
-- > isPackedEven :: PackBits Int -> Bool
-- > isPackedEven (Packed x) = even x
--
-- __Every__ pattern match, and data constructor call, performs a
-- \(\Theta(\log_{2}(\texttt{Cardinality a}))\) encoding or decoding operation.
-- Use with this in mind.
{-# COMPLETE Packed #-}
pattern Packed :: forall (a :: Type) .
(Finitary a, 1 <= Cardinality a) =>
a -> PackBits a
pattern Packed x <- (unpackBits -> x)
where Packed x = packBits x
instance (Finitary a, 1 <= Cardinality a) => Ord (PackBits a) where
{-# INLINABLE compare #-}
compare (PackedBits (BV.BitVec _ _ v1)) (PackedBits (BV.BitVec _ _ v2)) =
compare (PackedWords v1 :: PackWords a) (PackedWords v2 :: PackWords a)
instance NFData (PackBits a) where
{-# INLINE rnf #-}
rnf = rnf . op PackedBits
instance (Finitary a, 1 <= Cardinality a) => Finitary (PackBits a) where
type Cardinality (PackBits a) = Cardinality a
{-# INLINE fromFinite #-}
fromFinite = PackedBits . intoBits
{-# INLINE toFinite #-}
toFinite = outOfBits . op PackedBits
instance (Finitary a, 1 <= Cardinality a) => Bounded (PackBits a) where
{-# INLINE minBound #-}
minBound = start
{-# INLINE maxBound #-}
maxBound = end
newtype instance VU.MVector s (PackBits a) = MV_PackBits (VU.MVector s BV.Bit)
instance (Finitary a, 1 <= Cardinality a) => VGM.MVector VU.MVector (PackBits a) where
{-# INLINE basicLength #-}
basicLength = over MV_PackBits ((`div` bitLength @a) . VGM.basicLength)
{-# INLINE basicOverlaps #-}
basicOverlaps = over2 MV_PackBits VGM.basicOverlaps
{-# INLINABLE basicUnsafeSlice #-}
basicUnsafeSlice i len = over MV_PackBits (VGM.basicUnsafeSlice (i * bitLength @a) (len * bitLength @a))
{-# INLINABLE basicUnsafeNew #-}
basicUnsafeNew len = fmap MV_PackBits (VGM.basicUnsafeNew (len * bitLength @a))
{-# INLINE basicInitialize #-}
basicInitialize = VGM.basicInitialize . op MV_PackBits
{-# INLINABLE basicUnsafeRead #-}
basicUnsafeRead (MV_PackBits v) i = fmap PackedBits . VG.freeze . VGM.unsafeSlice (i * bitLength @a) (bitLength @a) $ v
{-# INLINABLE basicUnsafeWrite #-}
basicUnsafeWrite (MV_PackBits v) i (PackedBits x) = let slice = VGM.unsafeSlice (i * bitLength @a) (bitLength @a) v in
VG.unsafeCopy slice x
newtype instance VU.Vector (PackBits a) = V_PackBits (VU.Vector BV.Bit)
instance (Finitary a, 1 <= Cardinality a) => VG.Vector VU.Vector (PackBits a) where
{-# INLINE basicLength #-}
basicLength = over V_PackBits ((`div` bitLength @a) . VG.basicLength)
{-# INLINE basicUnsafeFreeze #-}
basicUnsafeFreeze = fmap V_PackBits . VG.basicUnsafeFreeze . op MV_PackBits
{-# INLINE basicUnsafeThaw #-}
basicUnsafeThaw = fmap MV_PackBits . VG.basicUnsafeThaw . op V_PackBits
{-# INLINABLE basicUnsafeSlice #-}
basicUnsafeSlice i len = over V_PackBits (VG.basicUnsafeSlice (i * bitLength @a) (len * bitLength @a))
{-# INLINABLE basicUnsafeIndexM #-}
basicUnsafeIndexM (V_PackBits v) i = pure . PackedBits . VG.unsafeSlice (i * bitLength @a) (bitLength @a) $ v
instance (Finitary a, 1 <= Cardinality a) => VU.Unbox (PackBits a)
-- | This wrapper provides an efficient 'Hashable' instance (hash the entire
-- underlying bit-packed vector, rather than each element individually), as well
-- as a 'Bin.Binary' instance (which stores or reads the entire blob of
-- bits \'in one go\').
newtype BulkPack a = BulkPack { exposeVector :: VU.Vector (PackBits a) }
deriving (NFData)
deriving instance (Finitary a, 1 <= Cardinality a) => Eq (BulkPack a)
deriving instance (Finitary a, 1 <= Cardinality a) => Ord (BulkPack a)
instance Hashable (BulkPack a) where
{-# INLINABLE hashWithSalt #-}
hashWithSalt salt = hashWithSalt salt . BV.cloneToWords . op V_PackBits . op BulkPack
instance Bin.Binary (BulkPack a) where
{-# INLINE put #-}
put = Bin.put . BV.cloneToWords . op V_PackBits . op BulkPack
{-# INLINE get #-}
get = BulkPack . V_PackBits . BV.castFromWords <$> Bin.get
-- Helpers
type BitLength a = CLog 2 (Cardinality a)
{-# INLINE packBits #-}
packBits :: forall (a :: Type) .
(Finitary a, 1 <= Cardinality a) =>
a -> PackBits a
packBits = fromFinite . toFinite
{-# INLINE unpackBits #-}
unpackBits :: forall (a :: Type) .
(Finitary a, 1 <= Cardinality a) =>
PackBits a -> a
unpackBits = fromFinite . toFinite
{-# INLINE bitLength #-}
bitLength :: forall (a :: Type) (b :: Type) .
(Finitary a, 1 <= Cardinality a, Num b) =>
b
bitLength = fromIntegral $ natVal' @(BitLength a) proxy#
{-# INLINABLE intoBits #-}
intoBits :: forall (n :: Nat) .
(KnownNat n, 1 <= n) =>
Finite n -> VU.Vector BV.Bit
intoBits f = BV.BitVec 0 nbBits wordArray
where
wordArray :: ByteArray
wordArray = intoWords f
nbBits :: Int
nbBits = fromIntegral $ natVal' @( CLog 2 n ) proxy#
{-# INLINABLE outOfBits #-}
outOfBits :: forall (n :: Nat) .
(KnownNat n) =>
VU.Vector BV.Bit -> Finite n
outOfBits (BV.BitVec _ _ wordArray) = outOfWords @n wordArray