{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
module Data.Vinyl.SRec (
SRec(..), toSRec, fromSRec
, sget, sput, slens
, srecGetSubset, srecSetSubset
, toSRec2, fromSRec2, SRec2(..)
, FieldOffset, FieldOffsetAux(..), StorableAt(..)
, peekField, pokeField
) where
import Data.Coerce (coerce)
import Data.Vinyl.Core
import Data.Vinyl.Functor (Lift(..), Compose(..), type (:.), ElField)
import Data.Vinyl.Lens (RecElem(..), RecSubset(..), type (⊆), RecElemFCtx)
import Data.Vinyl.TypeLevel (RImage, RIndex, Nat(..), RecAll, AllConstrained)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Ptr (Ptr)
import Foreign.Storable (Storable(..))
import System.IO.Unsafe (unsafePerformIO, unsafeDupablePerformIO)
import GHC.IO (IO(IO))
import GHC.Base (realWorld#)
import GHC.TypeLits (Symbol)
import GHC.Prim (MutableByteArray#, newAlignedPinnedByteArray#, byteArrayContents#)
import GHC.Prim (unsafeCoerce#, touch#, RealWorld)
import GHC.Ptr (Ptr(..))
import GHC.Types (Int(..))
data Bytes = Bytes (MutableByteArray# RealWorld)
newBytes :: Int -> IO Bytes
newBytes (I# n) = IO $ \s ->
case newAlignedPinnedByteArray# n 8# s of
(# s', mbarr #) -> (# s', Bytes mbarr #)
touchBytes :: Bytes -> IO ()
touchBytes (Bytes mbarr) = IO $ \s -> case touch# mbarr s of s' -> (# s', () #)
{-# INLINE touchBytes #-}
withBytesPtr :: Bytes -> (Ptr a -> IO r) -> IO r
withBytesPtr b@(Bytes mbarr) f = do
f (Ptr (byteArrayContents# (unsafeCoerce# mbarr))) <* touchBytes b
{-# INLINE withBytesPtr #-}
newtype ForeignPtr (a :: k) = ForeignPtr Bytes
withForeignPtr :: ForeignPtr a -> (Ptr b -> IO r) -> IO r
withForeignPtr (ForeignPtr b) = withBytesPtr b
{-# INLINE withForeignPtr #-}
mallocForeignPtrBytes :: Int -> IO (ForeignPtr a)
mallocForeignPtrBytes = fmap ForeignPtr . newBytes
{-# INLINE mallocForeignPtrBytes #-}
newtype SRec2 (g :: k -> *) (f :: k -> *) (ts :: [k]) =
SRec2 (ForeignPtr (Rec f ts))
newtype SRec f ts = SRecNT { getSRecNT :: SRec2 f f ts }
toSRec2 :: forall f ts. Storable (Rec f ts) => Rec f ts -> SRec2 f f ts
toSRec2 x = unsafePerformIO $ do
ptr <- mallocForeignPtrBytes (sizeOf (undefined :: Rec f ts))
SRec2 ptr <$ (withForeignPtr ptr (flip poke x))
{-# NOINLINE toSRec2 #-}
toSRec :: Storable (Rec f ts) => Rec f ts -> SRec f ts
toSRec = SRecNT . toSRec2
{-# INLINE toSRec #-}
fromSRec2 :: Storable (Rec f ts) => SRec2 g f ts -> Rec f ts
fromSRec2 (SRec2 ptr) = inlinePerformIO (withForeignPtr ptr peek)
{-# INLINE fromSRec2 #-}
fromSRec :: Storable (Rec f ts) => SRec f ts -> Rec f ts
fromSRec (SRecNT s) = fromSRec2 s
{-# INLINE fromSRec #-}
{-# INLINE inlinePerformIO #-}
inlinePerformIO :: IO a -> a
inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
data StorableAt f a where
StorableAt :: Storable (f a) => {-# UNPACK #-} !Int -> StorableAt f a
class (RIndex t ts ~ i, RecAll f ts Storable) => FieldOffsetAux f ts t i where
fieldOffset :: Int -> StorableAt f t
class FieldOffsetAux f ts t (RIndex t ts) => FieldOffset f ts t where
instance FieldOffsetAux f ts t (RIndex t ts) => FieldOffset f ts t where
instance (RecAll f (t ': ts) Storable) => FieldOffsetAux f (t ': ts) t 'Z where
fieldOffset !n = StorableAt n
{-# INLINE fieldOffset #-}
instance (RIndex t (s ': ts) ~ 'S i,
FieldOffsetAux f ts t i,
RecAll f (s ': ts) Storable)
=> FieldOffsetAux f (s ': ts) t ('S i) where
fieldOffset !n = fieldOffset @f @ts @t @i (n + sizeOf (undefined :: f s))
{-# INLINE fieldOffset #-}
pokeField :: forall f t ts. FieldOffset f ts t
=> ForeignPtr (Rec f ts) -> f t -> IO ()
pokeField fptr x = case fieldOffset @f @ts @t 0 of
StorableAt i -> withForeignPtr fptr $ \ptr ->
pokeByteOff ptr i x
{-# INLINE pokeField #-}
peekField :: forall f t ts. FieldOffset f ts t
=> ForeignPtr (Rec f ts) -> IO (f t)
peekField fptr = case fieldOffset @f @ts @t 0 of
StorableAt i -> withForeignPtr fptr $ \ptr ->
peekByteOff ptr i
{-# INLINE peekField #-}
sget :: forall f t ts. FieldOffset f ts t
=> SRec2 f f ts -> f t
sget (SRec2 ptr) = inlinePerformIO (peekField ptr)
{-# INLINE sget #-}
mallocAndCopy :: ForeignPtr a -> Int -> IO (ForeignPtr a)
mallocAndCopy src n = do
dst <- mallocForeignPtrBytes n
withForeignPtr src $ \src' ->
withForeignPtr dst $ \dst' ->
dst <$ copyBytes dst' src' n
sput :: forall (f :: u -> *) (t :: u) (ts :: [u]).
( FieldOffset f ts t
, Storable (Rec f ts)
, AllConstrained (FieldOffset f ts) ts)
=> f t -> SRec2 f f ts -> SRec2 f f ts
sput !x (SRec2 src) = unsafePerformIO $ do
let !n = sizeOf (undefined :: Rec f ts)
dst <- mallocAndCopy src n
SRec2 dst <$ pokeField dst x
{-# INLINE [1] sput #-}
pokeFieldUnsafe :: forall f t ts. FieldOffset f ts t
=> f t -> SRec2 f f ts -> SRec2 f f ts
pokeFieldUnsafe x y@(SRec2 ptr) = unsafeDupablePerformIO (y <$ pokeField ptr x)
{-# INLINE [1] pokeFieldUnsafe #-}
{-# RULES
"sput" forall x y z. sput x (sput y z) = pokeFieldUnsafe x (sput y z)
"sputUnsafe" forall x y z. sput x (pokeFieldUnsafe y z) = pokeFieldUnsafe x (pokeFieldUnsafe y z)
#-}
slens :: ( Functor g
, FieldOffset f ts t
, Storable (Rec f ts)
, AllConstrained (FieldOffset f ts) ts)
=> (f t -> g (f t)) -> SRec2 f f ts -> g (SRec2 f f ts)
slens f sr = fmap (flip sput sr) (f (sget sr))
{-# INLINE slens #-}
instance ( i ~ RIndex t ts
, FieldOffset ElField ts t
, Storable (Rec ElField ts)
, AllConstrained (FieldOffset ElField ts) ts)
=> RecElem (SRec2 ElField) t t ts ts i where
type RecElemFCtx (SRec2 ElField) f = f ~ ElField
rlensC = slens
{-# INLINE rlensC #-}
rgetC = sget
{-# INLINE rgetC #-}
rputC = sput
{-# INLINE rputC #-}
coerceSRec1to2 :: SRec f ts -> SRec2 f f ts
coerceSRec1to2 = coerce
coerceSRec2to1 :: SRec2 f f ts -> SRec f ts
coerceSRec2to1 = coerce
instance ( i ~ RIndex (t :: (Symbol,*)) (ts :: [(Symbol,*)])
, FieldOffset ElField ts t
, Storable (Rec ElField ts)
, AllConstrained (FieldOffset ElField ts) ts)
=> RecElem SRec (t :: (Symbol,*)) t (ts :: [(Symbol,*)]) ts i where
type RecElemFCtx SRec f = f ~ ElField
rlensC f = fmap coerceSRec2to1 . slens f . coerceSRec1to2
{-# INLINE rlensC #-}
rgetC = sget . coerceSRec1to2
{-# INLINE rgetC #-}
rputC x = coerceSRec2to1 . sput x . coerceSRec1to2
{-# INLINE rputC #-}
srecGetSubset :: forall (ss :: [u]) (rs :: [u]) (f :: u -> *).
(RPureConstrained (FieldOffset f ss) rs,
RPureConstrained (FieldOffset f rs) rs,
RFoldMap rs, RMap rs, RApply rs,
Storable (Rec f rs))
=> SRec2 f f ss -> SRec2 f f rs
srecGetSubset (SRec2 ptr) = unsafeDupablePerformIO $ do
dst <- mallocForeignPtrBytes (sizeOf (undefined :: Rec f rs))
SRec2 dst <$ (withForeignPtr dst $ \dst' ->
rfoldMap @rs unTagIO (peekSmallPokeBig dst'))
where peekers :: Rec (IO :. f) rs
peekers = rpureConstrained @(FieldOffset f ss) mkPeeker
{-# INLINE peekers #-}
mkPeeker :: FieldOffset f ss t => (IO :. f) t
mkPeeker = Compose (peekField ptr)
{-# INLINE mkPeeker #-}
pokers :: Ptr (Rec f rs) -> Rec (Poker f) rs
pokers dst = rpureConstrained @(FieldOffset f rs) (mkPoker dst)
{-# INLINE pokers #-}
mkPoker :: forall t. Ptr (Rec f rs) -> FieldOffset f rs t => Poker f t
mkPoker dst = case fieldOffset @f @rs @t 0 of
StorableAt i -> Lift (TaggedIO . pokeByteOff dst i)
{-# INLINE mkPoker #-}
peekNPoke :: (IO :. f) t -> Poker f t -> TaggedIO t
peekNPoke (Compose m) (Lift f) = TaggedIO (m >>= unTagIO . f)
{-# INLINE peekNPoke #-}
peekSmallPokeBig :: Ptr (Rec f rs) -> Rec TaggedIO rs
peekSmallPokeBig dst' = Lift . peekNPoke <<$>> peekers <<*>> pokers dst'
{-# INLINE srecGetSubset #-}
newtype TaggedIO a = TaggedIO { unTagIO :: IO () }
type Poker f = Lift (->) f TaggedIO
srecSetSubset :: forall (f :: u -> *) (ss :: [u]) (rs :: [u]).
(rs ⊆ ss,
RPureConstrained (FieldOffset f ss) rs,
RPureConstrained (FieldOffset f rs) rs,
RFoldMap rs, RMap rs, RApply rs,
Storable (Rec f ss))
=> SRec2 f f ss -> SRec2 f f rs -> SRec2 f f ss
srecSetSubset (SRec2 srcBig) (SRec2 srcSmall) = unsafeDupablePerformIO $ do
let n = sizeOf (undefined :: Rec f ss)
dst <- mallocForeignPtrBytes n
withForeignPtr srcBig $ \srcBig' ->
withForeignPtr dst $ \dst' ->
copyBytes dst' srcBig' n
SRec2 dst <$ (withForeignPtr dst $ \dst' ->
rfoldMap @rs unTagIO
(Lift . peekNPoke <<$>> peekers <<*>> pokers dst'))
where peekers :: Rec (IO :. f) rs
peekers = rpureConstrained @(FieldOffset f rs) mkPeeker
{-# INLINE peekers #-}
mkPeeker :: FieldOffset f rs t => (IO :. f) t
mkPeeker = Compose (peekField srcSmall)
pokers :: Ptr (Rec f ss) -> Rec (Poker f) rs
pokers dst = rpureConstrained @(FieldOffset f ss) (mkPoker dst)
{-# INLINE pokers #-}
mkPoker :: forall t. FieldOffset f ss t => Ptr (Rec f ss) -> Poker f t
mkPoker dst = case fieldOffset @f @ss @t 0 of
StorableAt i -> Lift (TaggedIO . pokeByteOff dst i)
{-# INLINE mkPoker #-}
peekNPoke :: (IO :. f) t -> Poker f t -> TaggedIO t
peekNPoke (Compose m) (Lift f) = TaggedIO (m >>= unTagIO . f)
{-# INLINE peekNPoke #-}
{-# INLINE srecSetSubset #-}
instance (is ~ RImage rs ss,
RecSubset Rec rs ss is,
Storable (Rec ElField rs),
Storable (Rec ElField ss),
RPureConstrained (FieldOffset ElField ss) rs,
RPureConstrained (FieldOffset ElField rs) rs,
RFoldMap rs, RMap rs, RApply rs)
=> RecSubset (SRec2 ElField) rs ss is where
type RecSubsetFCtx (SRec2 ElField) f = f ~ ElField
rsubsetC :: forall g. Functor g
=> (SRec2 ElField ElField rs -> g (SRec2 ElField ElField rs))
-> SRec2 ElField ElField ss
-> g (SRec2 ElField ElField ss)
rsubsetC f big@(SRec2 _) = fmap (srecSetSubset big) (f smallRec)
where smallRec :: SRec2 ElField ElField rs
smallRec = srecGetSubset big
{-# INLINE smallRec #-}
{-# INLINE rsubsetC #-}
instance (is ~ RImage rs ss,
RecSubset Rec rs ss is,
Storable (Rec ElField rs),
Storable (Rec ElField ss),
RPureConstrained (FieldOffset ElField ss) rs,
RPureConstrained (FieldOffset ElField rs) rs,
RFoldMap rs, RMap rs, RApply rs)
=> RecSubset SRec rs ss is where
type RecSubsetFCtx SRec f = f ~ ElField
rsubsetC f (SRecNT s) = SRecNT <$> rsubsetC (fmap getSRecNT . f . SRecNT) s
{-# INLINE rsubsetC #-}