PackWords.hs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. {-
  2. - Copyright (C) 2019 Koz Ross <koz.ross@retro-freedom.nz>
  3. -
  4. - This program is free software: you can redistribute it and/or modify
  5. - it under the terms of the GNU General Public License as published by
  6. - the Free Software Foundation, either version 3 of the License, or
  7. - (at your option) any later version.
  8. -
  9. - This program is distributed in the hope that it will be useful,
  10. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. - GNU General Public License for more details.
  13. -
  14. - You should have received a copy of the GNU General Public License
  15. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. -}
  17. {-# OPTIONS_GHC -fplugin GHC.TypeLits.Extra.Solver #-}
  18. {-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
  19. {-# LANGUAGE AllowAmbiguousTypes #-}
  20. {-# LANGUAGE TypeInType #-}
  21. {-# LANGUAGE RoleAnnotations #-}
  22. {-# LANGUAGE ScopedTypeVariables #-}
  23. {-# LANGUAGE ViewPatterns #-}
  24. {-# LANGUAGE PatternSynonyms #-}
  25. {-# LANGUAGE TypeFamilies #-}
  26. {-# LANGUAGE TypeApplications #-}
  27. {-# LANGUAGE TypeOperators #-}
  28. {-# LANGUAGE MultiParamTypeClasses #-}
  29. -- |
  30. -- Module: Data.Finitary.PackBytes
  31. -- Description: Scheme for packing @Finitary@ types into @Word@ arrays.
  32. -- Copyright: (C) Koz Ross 2019
  33. -- License: GPL version 3.0 or later
  34. -- Maintainer: koz.ross@retro-freedom.nz
  35. -- Stability: Experimental
  36. -- Portability: GHC only
  37. --
  38. -- If a type @a@ is 'Finitary', each inhabitant of @a@ has an index, which can
  39. -- be represented as an unsigned integer, spread across one or more machine
  40. -- words. This unsigned integer will have fixed length (as the number of
  41. -- inhabitants of @a@ is finite). We can use this to derive a 'VU.Unbox'
  42. -- instance, by representing 'VU.Vector' as a large array of machine words. We
  43. -- can also derive a 'Storable' instance similarly.
  44. --
  45. -- This is the most efficient encoding of an arbitrary finitary type, both due
  46. -- to the asymptotics of encoding and decoding (logarithmic in @Cardinality a@
  47. -- with base @Cardinality Word@) and the fact that word accesses are faster than
  48. -- byte and bit accesses on almost all architectures. Unless you have concerns
  49. -- regarding space, this encoding is a good choice.
  50. --
  51. -- Unless your type's cardinality is extremely large (a non-trivial multiple of
  52. -- @Cardinality Word@), this encoding is wasteful. If your type's cardinality is
  53. -- smaller than that of @Word@, you should consider "Data.Finitary.PackInto"
  54. -- instead, as you will have much larger control over space usage at almost no
  55. -- performance penalty.
  56. module Data.Finitary.PackWords
  57. (
  58. PackWords, pattern Packed
  59. ) where
  60. import Data.Vector.Binary ()
  61. import Data.Vector.Instances ()
  62. import GHC.TypeNats
  63. import Data.Proxy (Proxy(..))
  64. import GHC.TypeLits.Extra
  65. import CoercibleUtils (op, over, over2)
  66. import Data.Kind (Type)
  67. import Data.Finitary (Finitary(..))
  68. import Data.Finite (Finite)
  69. import Foreign.Storable (Storable(..))
  70. import Foreign.Ptr (castPtr, plusPtr)
  71. import Numeric.Natural (Natural)
  72. import Data.Hashable (Hashable(..))
  73. import Control.DeepSeq (NFData(..))
  74. import Control.Monad.Trans.State.Strict (evalState, get, modify, put)
  75. import Data.Semigroup (Dual(..))
  76. import qualified Data.Binary as Bin
  77. import qualified Data.Vector.Unboxed as VU
  78. import qualified Data.Vector.Generic as VG
  79. import qualified Data.Vector.Generic.Mutable as VGM
  80. -- | An opaque wrapper around @a@, representing each value as a fixed-length
  81. -- array of machine words.
  82. newtype PackWords (a :: Type) = PackWords (VU.Vector Word)
  83. deriving (Eq, Show)
  84. type role PackWords nominal
  85. -- | To provide (something that resembles a) data constructor for 'PackWords', we
  86. -- provide the following pattern. It can be used like any other data
  87. -- constructor:
  88. --
  89. -- > import Data.Finitary.PackWords
  90. -- >
  91. -- > anInt :: PackWords Int
  92. -- > anInt = Packed 10
  93. -- >
  94. -- > isPackedEven :: PackWords Int -> Bool
  95. -- > isPackedEven (Packed x) = even x
  96. --
  97. -- __Every__ pattern match, and data constructor call, performs a
  98. -- \(\Theta(\log_{\texttt{Cardinality Word}}(\texttt{Cardinality a}))\) encoding or decoding of @a@.
  99. -- Use with this in mind.
  100. pattern Packed :: forall (a :: Type) .
  101. (Finitary a, 1 <= Cardinality a) =>
  102. a -> PackWords a
  103. pattern Packed x <- (unpackWords -> x)
  104. where Packed x = packWords x
  105. instance Ord (PackWords a) where
  106. compare (PackWords v1) (PackWords v2) = getDual . VU.foldr go (Dual EQ) . VU.zipWith (,) v1 $ v2
  107. where go input order = (order <>) . Dual . uncurry compare $ input
  108. instance Bin.Binary (PackWords a) where
  109. {-# INLINE put #-}
  110. put = Bin.put . op PackWords
  111. {-# INLINE get #-}
  112. get = PackWords <$> Bin.get
  113. instance Hashable (PackWords a) where
  114. {-# INLINE hashWithSalt #-}
  115. hashWithSalt salt = hashWithSalt salt . op PackWords
  116. instance NFData (PackWords a) where
  117. {-# INLINE rnf #-}
  118. rnf = rnf . op PackWords
  119. instance (Finitary a, 1 <= Cardinality a) => Finitary (PackWords a) where
  120. type Cardinality (PackWords a) = Cardinality a
  121. {-# INLINE fromFinite #-}
  122. fromFinite = PackWords . intoWords
  123. {-# INLINE toFinite #-}
  124. toFinite = outOfWords . op PackWords
  125. instance (Finitary a, 1 <= Cardinality a) => Bounded (PackWords a) where
  126. {-# INLINE minBound #-}
  127. minBound = start
  128. {-# INLINE maxBound #-}
  129. maxBound = end
  130. instance (Finitary a, 1 <= Cardinality a) => Storable (PackWords a) where
  131. {-# INLINE sizeOf #-}
  132. sizeOf _ = wordLength @a * bytesPerWord
  133. {-# INLINE alignment #-}
  134. alignment _ = alignment (undefined :: Word)
  135. {-# INLINE peek #-}
  136. peek ptr = do let wordPtr = castPtr ptr
  137. PackWords <$> VU.generateM (wordLength @a) (peek . plusPtr wordPtr . (* bytesPerWord))
  138. {-# INLINE poke #-}
  139. poke ptr (PackWords v) = do let wordPtr = castPtr ptr
  140. VU.foldM'_ go wordPtr v
  141. where go p e = poke p e >> pure (plusPtr p bytesPerWord)
  142. newtype instance VU.MVector s (PackWords a) = MV_PackWords (VU.MVector s Word)
  143. instance (Finitary a, 1 <= Cardinality a) => VGM.MVector VU.MVector (PackWords a) where
  144. {-# INLINE basicLength #-}
  145. basicLength = over MV_PackWords ((`div` wordLength @a) . VGM.basicLength)
  146. {-# INLINE basicOverlaps #-}
  147. basicOverlaps = over2 MV_PackWords VGM.basicOverlaps
  148. {-# INLINE basicUnsafeSlice #-}
  149. basicUnsafeSlice i len = over MV_PackWords (VGM.basicUnsafeSlice (i * wordLength @a) (len * wordLength @a))
  150. {-# INLINE basicUnsafeNew #-}
  151. basicUnsafeNew len = MV_PackWords <$> VGM.basicUnsafeNew (len * wordLength @a)
  152. {-# INLINE basicInitialize #-}
  153. basicInitialize = VGM.basicInitialize . op MV_PackWords
  154. {-# INLINE basicUnsafeRead #-}
  155. basicUnsafeRead (MV_PackWords v) i = fmap PackWords . VG.freeze . VGM.unsafeSlice (i * wordLength @a) (wordLength @a) $ v
  156. {-# INLINE basicUnsafeWrite #-}
  157. basicUnsafeWrite (MV_PackWords v) i (PackWords x) = let slice = VGM.unsafeSlice (i * wordLength @a) (wordLength @a) v in
  158. VG.unsafeCopy slice x
  159. newtype instance VU.Vector (PackWords a) = V_PackWords (VU.Vector Word)
  160. instance (Finitary a, 1 <= Cardinality a) => VG.Vector VU.Vector (PackWords a) where
  161. {-# INLINE basicLength #-}
  162. basicLength = over V_PackWords ((`div` wordLength @a) . VG.basicLength)
  163. {-# INLINE basicUnsafeFreeze #-}
  164. basicUnsafeFreeze = fmap V_PackWords . VG.basicUnsafeFreeze . op MV_PackWords
  165. {-# INLINE basicUnsafeThaw #-}
  166. basicUnsafeThaw = fmap MV_PackWords . VG.basicUnsafeThaw . op V_PackWords
  167. {-# INLINE basicUnsafeSlice #-}
  168. basicUnsafeSlice i len = over V_PackWords (VG.basicUnsafeSlice (i * wordLength @a) (len * wordLength @a))
  169. {-# INLINE basicUnsafeIndexM #-}
  170. basicUnsafeIndexM (V_PackWords v) i = pure . PackWords . VG.unsafeSlice (i * wordLength @a) (wordLength @a) $ v
  171. instance (Finitary a, 1 <= Cardinality a) => VU.Unbox (PackWords a)
  172. -- Helpers
  173. type WordLength a = CLog (Cardinality Word) (Cardinality a)
  174. {-# INLINE bitsPerWord #-}
  175. bitsPerWord :: forall (a :: Type) .
  176. (Num a) =>
  177. a
  178. bitsPerWord = 8 * bytesPerWord
  179. {-# INLINE bytesPerWord #-}
  180. bytesPerWord :: forall (a :: Type) .
  181. (Num a) =>
  182. a
  183. bytesPerWord = fromIntegral . sizeOf $ (undefined :: Word)
  184. {-# INLINE wordLength #-}
  185. wordLength :: forall (a :: Type) (b :: Type) .
  186. (Finitary a, 1 <= Cardinality a, Num b) =>
  187. b
  188. wordLength = fromIntegral . natVal $ (Proxy :: Proxy (WordLength a))
  189. {-# INLINE packWords #-}
  190. packWords :: forall (a :: Type) .
  191. (Finitary a, 1 <= Cardinality a) =>
  192. a -> PackWords a
  193. packWords = fromFinite . toFinite
  194. {-# INLINE unpackWords #-}
  195. unpackWords :: forall (a :: Type) .
  196. (Finitary a, 1 <= Cardinality a) =>
  197. PackWords a -> a
  198. unpackWords = fromFinite . toFinite
  199. {-# INLINE intoWords #-}
  200. intoWords :: forall (n :: Nat) .
  201. (KnownNat n, 1 <= n) =>
  202. Finite n -> VU.Vector Word
  203. intoWords = evalState (VU.replicateM (wordLength @(Finite n)) go) . fromIntegral @_ @Natural
  204. where go = do remaining <- get
  205. let (d, r) = quotRem remaining bitsPerWord
  206. put d >> pure (fromIntegral r)
  207. {-# INLINE outOfWords #-}
  208. outOfWords :: forall (n :: Nat) .
  209. (KnownNat n) =>
  210. VU.Vector Word -> Finite n
  211. outOfWords v = evalState (VU.foldM' go 0 v) 1
  212. where go old w = do power <- get
  213. let placeValue = power * fromIntegral w
  214. modify (* bitsPerWord)
  215. return (old + placeValue)