packages feed

compact-mutable-0.1: src/Data/Primitive/Compact.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE Strict #-}

{-# OPTIONS_GHC -O2 #-}

module Data.Primitive.Compact
  ( CompactMutableArray(..)
  , CompactMutablePrimArray(..) -- should not actually export this
  , CompactMutableByteArray(..)
  , ContractedMutableArray(..)
  , CompactPrimRef
  , Token
  , Contractible(..)
  , Heap(..)
  , withToken
  , newCompactArray
  , newContractedArray
  , newCompactArrayCopier
  , newCompactPrimRef
  , newCompactPrimArray
  , newCompactPrimArrayCopier
  , readCompactArray
  , writeCompactArray
  , copyCompactMutableArray
  , copyContractedMutableArray
  , getSizeOfCompact
  , readContractedArray
  , writeContractedArray
  -- * Unsafe things
  , compactAddGeneral
  , unsafeInsertCompactArray
  , unsafeInsertContractedArray
  , printCompactArrayAddrs
  , unsafeUnliftedToAddr
  , unsafeUnliftedFromAddr
  ) where

import GHC.Int
import GHC.Prim
import GHC.Compact
import Control.Monad
import Control.Monad.Primitive
import Data.Primitive
import Data.Primitive.SmallArray
import Data.Primitive.Array
import Data.Primitive.PrimArray
import Data.Primitive.Types
import Data.Primitive.PrimRef
import Data.Primitive.ByteArray
import Data.Primitive.UnliftedArray
import Data.Proxy
import Unsafe.Coerce
import Debug.Trace
import GHC.Conc (pseq)
import GHC.Ptr (Ptr(..))
import GHC.Word (Word(..))
import GHC.Types

-- | This represents a mutable array in a compact region.
newtype CompactMutableArray s (a :: Heap -> *) (c :: Heap)
  = CompactMutableArray (MutablePrimArray s Addr) -- (Array (a c))
newtype CompactPrimRef s a c
  = CompactPrimRef (PrimRef s a)
newtype CompactMutablePrimArray s a c
  = CompactMutablePrimArray (MutablePrimArray s a)
newtype CompactMutableByteArray s c
  = CompactMutableByteArray (MutableByteArray s)

-- | A contracted array is like a prim array, except that
--   it talks about data structures who have data on a
--   compact heap.
data ContractedMutableArray (a :: * -> Heap -> *) s (c :: Heap)
  = ContractedMutableArray
    (MutableByteArray# s)
    (MutableArrayArray# s)
data Token c = Token Compact#
data Heap

-- withToken :: PrimMonad m => (forall c. Token c -> m x) -> m x
-- withToken f = compactNewGeneral >>= f

withToken :: PrimMonad m => (forall c. Token c -> m x) -> m x
withToken f = do
  !token <- compactNewGeneral
  !x <- f token
  !_ <- compactAddGeneral token (42 :: Int)
  return x

compactNewGeneral# :: Word# -> State# s -> (# State# s, Compact# #)
compactNewGeneral# = unsafeCoerce compactNew#

compactAddGeneral# :: Compact# -> a -> State# s -> (# State# s, a #)
compactAddGeneral# = unsafeCoerce compactAdd#

compactSizeGeneral# :: Compact# -> State# s -> (# State# s, Word# #)
compactSizeGeneral# = unsafeCoerce compactSize#


-- Does not typecheck
-- addByteArray :: Compact# -> State# RealWorld -> State# RealWorld
-- addByteArray c s1 = case newByteArray# 50# s1 of
--   (# s2, arr #) -> case compactAdd# c arr s2 of
--     (# s3, arr2 #) -> s3

compactAddGeneral :: PrimMonad m => Token c -> a -> m a
compactAddGeneral (Token compact#) !a = primitive $ \ !s ->
  compactAddGeneral# compact# a s

-- Notice that @c@ is universally quantified here.
compactNewGeneral :: PrimMonad m => m (Token c)
compactNewGeneral = primitive $ \ !s ->
  case compactNewGeneral# 31268## s of { (# !s1, !c #) ->
    (# s1, Token c #) }

getSizeOfCompact :: PrimMonad m => Token c -> m Word
getSizeOfCompact (Token compact#) = primitive $ \s0 ->
   case compactSizeGeneral# compact# s0 of (# s1, sz #) -> (# s1, W# sz #)

-- | Create a new mutable array.
newCompactPrimRef :: (PrimMonad m, Prim a)
  => Token c
  -> a -- ^ initial value
  -> m (CompactPrimRef (PrimState m) a c)
newCompactPrimRef !c !a = do
  !ref1 <- newPrimRef a
  !ref2 <- compactAddGeneral c ref1
  return (CompactPrimRef ref2)

newCompactPrimArray :: (PrimMonad m, Prim a)
  => Token c
  -> Int -- ^ Length
  -> m (CompactMutablePrimArray (PrimState m) a c)
newCompactPrimArray !c !n = do
  !ref1 <- newPrimArray n
  !ref2 <- compactAddGeneral c ref1
  return (CompactMutablePrimArray ref2)

newCompactPrimArrayCopier :: (PrimMonad m, Prim a)
  => Token c
  -> Int -- ^ Length
  -> m (m (CompactMutablePrimArray (PrimState m) a c))
newCompactPrimArrayCopier !c !n = do
  !marr1 <- newPrimArray n
  return $ do
    !marr2 <- compactAddGeneral c marr1
    return (CompactMutablePrimArray marr2)

-- | Create a new mutable array. Notice that we do not need a
--   default value.
newCompactArray :: (PrimMonad m)
  => Token c
  -> Int
  -> m (CompactMutableArray (PrimState m) a c)
newCompactArray !c !n = do
  -- it is unfortunate that we have to do this allocation twice,
  -- once on the normal heap and once on the compact heap.
  !marr1 <- newPrimArray n
  !marr2 <- compactAddGeneral c marr1
  return (CompactMutableArray marr2)

newContractedArray :: forall m a c. (PrimMonad m, Contractible a)
  => Token c
  -> Int
  -> m (ContractedMutableArray a (PrimState m) c)
newContractedArray !c !n = do
  -- it is unfortunate that we have to do this allocation twice,
  -- once on the normal heap and once on the compact heap.
  MutableByteArray ba <- compactAddGeneral c =<< newByteArray (n * I# (unsafeContractedByteCount# (proxy# :: Proxy# a)))
  UnliftedArray aa <- compactAddGeneral c =<< unsafeFreezeUnliftedArray =<< newUnliftedArray (n * I# (unsafeContractedUnliftedPtrCount# (proxy# :: Proxy# a))) (MutableByteArray ba)
  return (ContractedMutableArray ba (unsafeCoerce# aa))

newCompactArrayCopier :: PrimMonad m
  => Token c
  -> Int
  -> m (m (CompactMutableArray (PrimState m) a c))
newCompactArrayCopier !c !n = do
  !marr1 <- newPrimArray n
  return $ do
    !marr2 <- compactAddGeneral c marr1
    return (CompactMutableArray marr2)

writeCompactArray :: PrimMonad m
  => CompactMutableArray (PrimState m) a c
  -> Int
  -> a c -- ^ please do not pick something that has been unboxed into 
         --   a data constructor, segfaults lie that way.
  -> m ()
writeCompactArray (CompactMutableArray !marr) !ix !val = do
  let !addr = unsafeToAddr val
  -- traceM (showAddr addr)
  writePrimArray marr ix addr
{-# INLINE writeCompactArray #-}

readCompactArray :: PrimMonad m
  => CompactMutableArray (PrimState m) a c
  -> Int 
  -> m (a c)
readCompactArray (CompactMutableArray !marr) !ix = do
  !addr <- readPrimArray marr ix
  return (unsafeFromAddr addr)
{-# INLINE readCompactArray #-}

-- | Insert an element in the array, shifting the values right 
--   of the index. The array size should be big enough for this
--   shift, this is not checked.
unsafeInsertCompactArray :: PrimMonad m
  => Int -- ^ Size of the original array
  -> Int -- ^ Index
  -> a c -- ^ Value
  -> CompactMutableArray (PrimState m) a c -- ^ Array to modify
  -> m ()
unsafeInsertCompactArray !sz !i !x (CompactMutableArray !marr) = do
  copyMutablePrimArray marr (i + 1) marr i (sz - i)
  writePrimArray marr i (unsafeToAddr x)
{-# INLINE unsafeInsertCompactArray #-}

unsafeInsertContractedArray :: forall m a c. (PrimMonad m, Contractible a)
  => Int -- ^ Size of the original array
  -> Int -- ^ Index
  -> a (PrimState m) c -- ^ Value
  -> ContractedMutableArray a (PrimState m) c -- ^ Array to modify
  -> m ()
unsafeInsertContractedArray !sz !i !x !carr = do
  copyContractedMutableArray carr (i + 1) carr i (sz - i)
  writeContractedArray carr i x
{-# INLINE unsafeInsertContractedArray #-}

copyContractedMutableArray
  :: forall m a c. (PrimMonad m, Contractible a)
  => ContractedMutableArray a (PrimState m) c -- ^ destination
  -> Int -- ^ destination offset
  -> ContractedMutableArray a (PrimState m) c -- ^ source
  -> Int -- ^ source offset
  -> Int -- ^ length
  -> m ()
copyContractedMutableArray (ContractedMutableArray destB destA) (I# destOff) (ContractedMutableArray srcB srcA) (I# srcOff) (I# len)
  = primitive_ $ \s1 -> case copyMutableByteArray# srcB (srcOff *# (unsafeContractedByteCount# (proxy# :: Proxy# a))) destB (destOff *# (unsafeContractedByteCount# (proxy# :: Proxy# a))) (len *# (unsafeContractedByteCount# (proxy# :: Proxy# a))) s1 of
      s2 -> copyMutableArrayArray# srcA (srcOff *# (unsafeContractedUnliftedPtrCount# (proxy# :: Proxy# a))) destA (destOff *# (unsafeContractedUnliftedPtrCount# (proxy# :: Proxy# a))) (len *# (unsafeContractedUnliftedPtrCount# (proxy# :: Proxy# a))) s2
{-# INLINE copyContractedMutableArray #-}

copyCompactMutableArray
  :: PrimMonad m
  => CompactMutableArray (PrimState m) a c -- ^ destination
  -> Int -- ^ destination offset
  -> CompactMutableArray (PrimState m) a c -- ^ source
  -> Int -- ^ source offset
  -> Int -- ^ length
  -> m ()
copyCompactMutableArray (CompactMutableArray !dest) !doff (CompactMutableArray !src) !soff !len
  = copyMutablePrimArray dest doff src soff len
{-# INLINE copyCompactMutableArray #-}

unsafeToAddr :: a -> Addr
unsafeToAddr a = pseq a (Addr (unsafeCoerce# a :: Addr#))
{-# INLINE unsafeToAddr #-}

-- not joking when I say that this is about as unsafe as it gets.
unsafeFromAddr :: Addr -> a
unsafeFromAddr (Addr x) = unsafeCoerce# x
{-# INLINE unsafeFromAddr #-}

-- make sure this is actually sound. I'm pretty sure that
-- unlifted data must already be fully evaluated because of
-- how its calling convention works.
unsafeUnliftedToAddr :: forall (a :: TYPE 'UnliftedRep). a -> Addr
unsafeUnliftedToAddr a = Addr (unsafeCoerce# a :: Addr#)
{-# INLINE unsafeUnliftedToAddr #-}

unsafeUnliftedFromAddr :: forall (a :: TYPE 'UnliftedRep). Addr -> a
unsafeUnliftedFromAddr (Addr x) = unsafeCoerce# x
{-# INLINE unsafeUnliftedFromAddr #-}

showAddr :: Addr -> String
showAddr (Addr a#) = show (Ptr a#)

printCompactArrayAddrs :: CompactMutableArray RealWorld a c -> IO ()
printCompactArrayAddrs (CompactMutableArray marr) = do
  n <- getSizeofMutablePrimArray marr
  forM_ [0..(n - 1)] $ \ix -> do
    addr <- readPrimArray marr ix
    putStrLn (showAddr addr)

writeContractedArray :: (PrimMonad m, Contractible a)
  => ContractedMutableArray a (PrimState m) c -> Int -> a (PrimState m) c -> m ()
writeContractedArray (ContractedMutableArray ba aa) (I# ix) r =
  primitive_ $ \s -> writeContractedArray# ba aa ix r s

readContractedArray :: (PrimMonad m, Contractible a)
  => ContractedMutableArray a (PrimState m) c -> Int -> m (a (PrimState m) c)
readContractedArray (ContractedMutableArray ba aa) (I# ix) =
  primitive $ \s -> readContractedArray# ba aa ix s

-- | Super dangerous typeclass. Be careful trying to
--   define instances.
class Contractible (a :: * -> Heap -> *) where
  unsafeContractedUnliftedPtrCount# :: Proxy# a -> Int#
  -- ^ Number of unlifted pointers
  unsafeContractedByteCount# :: Proxy# a -> Int#
  -- ^ size of serialization, in bytes
  writeContractedArray# :: MutableByteArray# s -> MutableArrayArray# s -> Int# -> a s c -> State# s -> State# s
  -- ^ remember that the int provided here gives an index in
  --   elements, not in bytes
  readContractedArray# :: MutableByteArray# s -> MutableArrayArray# s -> Int# -> State# s -> (# State# s, a s c #)
  -- ^ index is in elements, not bytes

-- instance Contractible (CompactMutableArray m a) where
--   unsafeContractedUnliftedPtrCount _ = 1
--   unsafeContractedByteCount _ = 0
--   unsafeWriteContractedArray (ContractedMutableArray _ marr#) (I# ix) (CompactMutableArray (MutablePrimArray arr#)) =
--     primitive_ (\s -> writeMutableByteArrayArray# marr# ix arr# s)
--   unsafeReadContractedArray (ContractedMutableArray _ marr#) (I# ix) =
--     primitive (\s -> case readMutableByteArrayArray# marr# ix s of
--       (# s', arr# #) -> (# s', CompactMutableArray (MutablePrimArray arr# ) #)
--     )