packages feed

derive-prim-0.1.0.0: src/Data/Primitive/Generic.hs

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Data.Primitive.Generic
  ( Align(..), Packed
  , GenericPrim(..)
  ) where
import Data.Semigroup
import Data.Functor.Identity
import Data.Functor.Const
import Data.Complex 
import Data.Primitive.ByteArray.Unaligned
import Data.Primitive
import Data.Proxy
import Data.Kind (Type)

import Foreign.C
import Foreign.Ptr

import System.Posix.Types

import GHC.Generics
import GHC.Stable
import GHC.TypeLits
import GHC.Exts hiding (setByteArray#)

-- |A wrapper that modify the wrapped type alignment
newtype Align (a :: Nat) t = Align t
  deriving newtype (PrimUnaligned)

-- |Modify type to have alignment of 1
type Packed = Align 1
type family ValidAlign (n :: Nat) :: Constraint where
  ValidAlign 0 = TypeError (Text "Alignment must be strictly possitive (> 0)")
  ValidAlign _ = ()

instance (ValidAlign a, KnownNat a, Prim t) => Prim (Align a t) where
  alignmentOfType# _ = align#
    where !(I# align#) = fromIntegral (natVal @a Proxy)
  sizeOfType# _ = sizeOfType# @t Proxy
  indexByteArray# ba# idx# = Align (indexByteArray# ba# idx#)
  readByteArray# ba# idx# state# =
    let !(# state'#, value #) = readByteArray# ba# idx# state#
    in  (# state'#, Align value #)
  writeByteArray# ba# idx# (Align value) = writeByteArray# ba# idx# value

  indexOffAddr# addr# idx# = Align (indexOffAddr# addr# idx#)
  readOffAddr# addr# idx# state# =
    let !(# state'#, value #) = readOffAddr# addr# idx# state# 
    in  (# state'#, Align value #)
  writeOffAddr# addr# idx# (Align value) = writeOffAddr# addr# idx# value

-- |Derive Prim and PrimUnaligned instances
newtype GenericPrim a = GenericPrim a

-- TODO: Implement a plugin to make the performance not terrible
instance (Generic a, DerivePrim (Rep a)) => PrimUnaligned (GenericPrim a) where
  indexUnalignedByteArray# ba# offs# = GenericPrim (to (implIndexUnalignedByteArray# (membOffsets @(Rep a)) ba# offs#))
  readUnalignedByteArray# ba# offs# state# =
    let !(# state'#, value #) = implReadUnalignedByteArray# (membOffsets @(Rep a)) ba# offs# state#
    in  (# state'#, GenericPrim (to value) #)
  writeUnalignedByteArray# ba# offs# (GenericPrim (from -> value)) = implWriteUnalignedByteArray# (membOffsets @(Rep a)) ba# offs# value

instance (Generic a, DerivePrim (Rep a)) => Prim (GenericPrim a) where
  sizeOfType# _ = structSize# @(Rep a) Proxy
  alignmentOfType# _ = structAlign# @(Rep a) Proxy

  indexByteArray# ba# idx# = GenericPrim (to (implIndexUnalignedByteArray# (membOffsets @(Rep a)) ba# (idx# *# structSize# @(Rep a) Proxy)))
  readByteArray# ba# idx# state# =
    let !(# state'#, value #) = implReadUnalignedByteArray# (membOffsets @(Rep a)) ba# (idx# *# structSize# @(Rep a) Proxy) state# 
    in  (# state'#, GenericPrim (to value) #)
  writeByteArray# ba# idx# (GenericPrim (from -> value)) = implWriteUnalignedByteArray# (membOffsets @(Rep a)) ba# (idx# *# structSize# @(Rep a) Proxy) value

  indexOffAddr# addr# idx# = GenericPrim (to (implIndexUnalignedOffAddr# (membOffsets @(Rep a)) (addr# `plusAddr#` (idx# *# structSize# @(Rep a) Proxy))))
  readOffAddr# addr# idx# state# =
    let !(# state'#, value #) = implReadUnalignedOffAddr# (membOffsets @(Rep a)) (addr# `plusAddr#` (idx# *# structSize# @(Rep a) Proxy)) state#
    in  (# state'#, GenericPrim (to value) #)
  writeOffAddr# addr# idx# (GenericPrim (from -> value)) = implWriteUnalignedOffAddr# (membOffsets @(Rep a)) (addr# `plusAddr#` (idx# *# structSize# @(Rep a) Proxy)) value

-- Base must be strictly possitive ( > 0 )
alignTo# :: Int# -> Int# -> Int#
alignTo# offs# base# = res#
  where padded# = offs# +# base# -# 1#
        res# = padded# -# (padded# `remInt#` base#)

class DerivePrim q where
  type DeriveTree q :: Type -> Type
  inOrder' :: (Int# -> (# Int#, Int# #) -> Int#) -> Int# -> Int#
  --         accum     alignment size        output  accum'   in accum  out accum
  inOrder  :: (Int# -> (# Int#, Int# #) -> (# Int#, Int# #)) -> Int# -> (# DeriveTree q p, Int# #)

  -- NOTE: Offset are always in bytes
  implIndexUnalignedByteArray# :: DeriveTree q p -> ByteArray# -> Int# -> q p
  implReadUnalignedByteArray#  :: DeriveTree q p -> MutableByteArray# s -> Int# -> State# s -> (# State# s, q p #)
  implWriteUnalignedByteArray# :: DeriveTree q p -> MutableByteArray# s -> Int# -> q p -> State# s -> State# s

  -- TODO: Maybe remove the index since that can be implemented by plusAddr#
  implIndexUnalignedOffAddr# :: DeriveTree q p -> Addr# -> q p
  implReadUnalignedOffAddr#  :: DeriveTree q p -> Addr# -> State# s -> (# State# s, q p #)
  implWriteUnalignedOffAddr# :: DeriveTree q p -> Addr# -> q p -> State# s -> State# s

membOffsets :: forall q p. DerivePrim q => DeriveTree q p
membOffsets = tree
  where (# tree, _ #) = inOrder @q fn 0# 
        fn accum# (# align#, size# #) =
          let offs# = accum# `alignTo#` align#
          in  (# offs#, offs# +# size# #)

membAligns :: forall q p. DerivePrim q => DeriveTree q p
membAligns = tree
  where (# tree, _ #) = inOrder @q fn 0# 
        fn (_ :: Int#) (# align# :: Int# , _ :: Int# #) = 
          (# align#, 0# #)

membSizes :: forall q p. DerivePrim q => DeriveTree q p
membSizes = tree
  where (# tree, _ #) = inOrder @q fn 0# 
        fn (_ :: Int#) (# _ :: Int# , size# :: Int# #) = 
          (# size#, 0# #)

structSize# :: forall q. DerivePrim q => Proxy q -> Int#
structSize# p = inOrder' @q fn 0# `alignTo#` structAlign# p
  where fn offset# (# align#, size# #) = (offset# `alignTo#` align#) +# size#

structAlign# :: forall q. DerivePrim q => Proxy q -> Int#
structAlign# _ = inOrder' @q fn 1#
  where fn (I# -> accum) (# I# -> align, _ :: Int# #) = 
          let !(I# result#) = lcm accum align
          in  result#

instance DerivePrim V1 where 
  type DeriveTree V1 = V1
  inOrder' _ a = a
  inOrder  _ a = (# undefined, a #)

  implIndexUnalignedByteArray# _ _ _ = undefined
  implReadUnalignedByteArray# _ _ _ state# = (# state#, undefined #)
  implWriteUnalignedByteArray# _ _ _ _ state# = state#

  implIndexUnalignedOffAddr# _ _ = undefined
  implReadUnalignedOffAddr# _ _ state# = (# state#, undefined #)
  implWriteUnalignedOffAddr# _ _ _ state# = state#


instance DerivePrim U1 where
  type DeriveTree U1 = U1
  inOrder' fn a = fn a (# 1#, 0# #)
  inOrder  fn a = (# U1, a' #)
    where !(# _, a' #) = fn a (# 1#, 0# #)

  implIndexUnalignedByteArray# _ _ _ = U1
  implReadUnalignedByteArray# _ _ _ state# = (# state#, U1 #)
  implWriteUnalignedByteArray# _ _ _ _ state# = state#

  implIndexUnalignedOffAddr# _ _ = U1
  implReadUnalignedOffAddr# _ _ state# = (# state#, U1 #)
  implWriteUnalignedOffAddr# _ _ _ state# = state#

instance (DerivePrim f, DerivePrim g) => DerivePrim (f :*: g) where
  type DeriveTree (f :*: g) = DeriveTree f :*: DeriveTree g
  inOrder' fn a = inOrder' @g fn (inOrder' @f fn a)
  inOrder  fn a = (# f :*: g, g# #)
    where !(# f, f# #) = inOrder @f fn a
          !(# g, g# #) = inOrder @g fn f#

  implIndexUnalignedByteArray# (fi :*: gi) ba# baseOffs# = 
    implIndexUnalignedByteArray# fi ba# baseOffs# :*:
    implIndexUnalignedByteArray# gi ba# baseOffs#
  implReadUnalignedByteArray# (fi :*: gi) ba# baseOffs# state0# =
    let !(# state1#, f #) = implReadUnalignedByteArray# @f fi ba# baseOffs# state0#
        !(# state2#, g #) = implReadUnalignedByteArray# @g gi ba# baseOffs# state1#
    in  (# state2#, f :*: g #)
  implWriteUnalignedByteArray# (fi :*: gi) ba# baseOffs# (f :*: g) state# =
    implWriteUnalignedByteArray# @g gi ba# baseOffs# g (implWriteUnalignedByteArray# @f fi ba# baseOffs# f state#)

  implIndexUnalignedOffAddr# (fi :*: gi) addr# = 
    implIndexUnalignedOffAddr# fi addr# :*:
    implIndexUnalignedOffAddr# gi addr# 
  implReadUnalignedOffAddr# (fi :*: gi) addr# state0# =
    let !(# state1#, f #) = implReadUnalignedOffAddr# @f fi addr# state0#
        !(# state2#, g #) = implReadUnalignedOffAddr# @g gi addr# state1#
    in  (# state2#, f :*: g #)
  implWriteUnalignedOffAddr# (fi :*: gi) addr# (f :*: g) state# =
    implWriteUnalignedOffAddr# @g gi addr# g (implWriteUnalignedOffAddr# @f fi addr# f state#)

instance DerivePrim f => DerivePrim (M1 i t f) where
  type DeriveTree (M1 i t f) = M1 i t (DeriveTree f)
  inOrder' = inOrder' @f
  inOrder fn a = (# M1 f, f# #)
    where !(# f, f# #) = inOrder @f fn a 

  implIndexUnalignedByteArray# (M1 fi) ba# baseOffs# = M1 (implIndexUnalignedByteArray# fi ba# baseOffs#)
  implReadUnalignedByteArray# (M1 fi) ba# baseOffs# state# = 
    let !(# state'#, value #) = implReadUnalignedByteArray# fi ba# baseOffs# state# 
    in  (# state'#, M1 value #)
  implWriteUnalignedByteArray# (M1 fi) ba# baseOffs# (M1 value) =
    implWriteUnalignedByteArray# fi ba# baseOffs# value


  implIndexUnalignedOffAddr# (M1 fi) addr# = M1 (implIndexUnalignedOffAddr# fi addr#)
  implReadUnalignedOffAddr# (M1 fi) addr# state# = 
    let !(# state'#, value #) = implReadUnalignedOffAddr# fi addr# state# 
    in  (# state'#, M1 value #)
  implWriteUnalignedOffAddr# (M1 fi) addr# (M1 value) =
    implWriteUnalignedOffAddr# fi addr# value

-- NOTE: The reliant on PrimUnaligned is a little problematic since PrimUnaligned does 
-- not implement all instances of Prim (Some notable include StablePtr)
instance (Prim c, PrimUnaligned c) => DerivePrim (K1 i c) where
  type DeriveTree (K1 i c) = K1 i Int

  inOrder' fn a = fn a (# alignmentOfType# @c Proxy, sizeOfType# @c Proxy #)
  inOrder  fn a = (# K1 v, a' #)
    where !(# I# -> v, a' #) = fn a (# alignmentOfType# @c Proxy, sizeOfType# @c Proxy #)

  implIndexUnalignedByteArray# (K1 (I# membOffs#)) ba# baseOffs# = K1 (indexUnalignedByteArray# ba# (baseOffs# +# membOffs#))
  implReadUnalignedByteArray# (K1 (I# membOffs#)) ba# baseOffs# state# = (# state'#, K1 value #)
    where !(# state'#, value #) = readUnalignedByteArray# ba# (baseOffs# +# membOffs#) state#
  implWriteUnalignedByteArray# (K1 (I# membOffs#)) ba# baseOffs# (K1 value) =
    writeUnalignedByteArray# ba# (baseOffs# +# membOffs#) value

  implIndexUnalignedOffAddr# (K1 (I# membOffs#)) addr# = K1 (indexOffAddr# (addr# `plusAddr#` membOffs#) 0#)
  implReadUnalignedOffAddr# (K1 (I# membOffs#)) addr# state# = (# state'#, K1 value #)
    where !(# state'#, value #) = readOffAddr# (addr# `plusAddr#` membOffs#) 0# state#
  implWriteUnalignedOffAddr# (K1 (I# membOffs#)) addr# (K1 value) =
    writeOffAddr# (addr# `plusAddr#` membOffs#) 0# value

-- Extra PrimUnaligned instances
instance PrimUnaligned (StablePtr a) where
  indexUnalignedByteArray# ba# offset# = StablePtr (indexWord8ArrayAsStablePtr# ba# offset#)
  readUnalignedByteArray# ba# offset# state# = 
    let !(# state'#, ptr# #) = readWord8ArrayAsStablePtr# ba# offset# state# 
    in  (# state'#, StablePtr ptr# #)
  writeUnalignedByteArray# ba# offset# (StablePtr ptr#) = writeWord8ArrayAsStablePtr# ba# offset# ptr#
instance PrimUnaligned (FunPtr a) where
  indexUnalignedByteArray# ba# offset# = FunPtr (indexWord8ArrayAsAddr# ba# offset#)
  readUnalignedByteArray# ba# offset# state# = 
    let !(# state'#, ptr# #) = readWord8ArrayAsAddr# ba# offset# state# 
    in  (# state'#, FunPtr ptr# #)
  writeUnalignedByteArray# ba# offset# (FunPtr ptr#) = writeWord8ArrayAsAddr# ba# offset# ptr#
instance (Prim a, PrimUnaligned a) => PrimUnaligned (Complex a) where
  indexUnalignedByteArray# ba# offset# =
    indexUnalignedByteArray# ba# offset# :+
    indexUnalignedByteArray# ba# (offset# +# sizeOfType# @a Proxy)
  readUnalignedByteArray# ba# offset# state0# = 
    let !(# state1#, a #) = readUnalignedByteArray# ba# offset# state0# 
        !(# state2#, b #) = readUnalignedByteArray# ba# (offset# +# sizeOfType# @a Proxy) state1#
    in  (# state2#, a :+ b #)
  writeUnalignedByteArray# ba# offset# (a :+ b) state# =
    writeUnalignedByteArray# ba# (offset# +# sizeOfType# @a Proxy) b (writeUnalignedByteArray# ba# offset# a state#)

-- Derive missing instances
deriving newtype instance PrimUnaligned CBool
deriving newtype instance PrimUnaligned CClock
deriving newtype instance PrimUnaligned CFloat
deriving newtype instance PrimUnaligned CIntMax
deriving newtype instance PrimUnaligned CUIntMax
deriving newtype instance PrimUnaligned CIntPtr
deriving newtype instance PrimUnaligned CUIntPtr
deriving newtype instance PrimUnaligned CPtrdiff
deriving newtype instance PrimUnaligned CUSeconds
deriving newtype instance PrimUnaligned CSUSeconds
deriving newtype instance PrimUnaligned CSigAtomic
deriving newtype instance PrimUnaligned CSize
deriving newtype instance PrimUnaligned CTime
deriving newtype instance PrimUnaligned CUChar
deriving newtype instance PrimUnaligned CUShort
deriving newtype instance PrimUnaligned CWchar
deriving newtype instance PrimUnaligned IntPtr
deriving newtype instance PrimUnaligned WordPtr
deriving newtype instance PrimUnaligned CBlkCnt
deriving newtype instance PrimUnaligned CBlkSize
deriving newtype instance PrimUnaligned CCc
deriving newtype instance PrimUnaligned CClockId
deriving newtype instance PrimUnaligned CFsBlkCnt
deriving newtype instance PrimUnaligned CFsFilCnt
deriving newtype instance PrimUnaligned CGid
deriving newtype instance PrimUnaligned CId
deriving newtype instance PrimUnaligned CKey
deriving newtype instance PrimUnaligned CNlink
deriving newtype instance PrimUnaligned CRLim
deriving newtype instance PrimUnaligned CSpeed
deriving newtype instance PrimUnaligned CTcflag
deriving newtype instance PrimUnaligned CTimer
deriving newtype instance PrimUnaligned CUid
deriving newtype instance PrimUnaligned a => PrimUnaligned (Identity a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Down a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (First a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Last a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Max a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Min a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Dual a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Product a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Sum a)
deriving newtype instance PrimUnaligned a => PrimUnaligned (Const a b)