packages feed

packed-data-0.2.0.0: src/Data/Packed/Needs.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnboxedTuples #-}

module Data.Packed.Needs (
    -- * Type
    Needs (..),

    -- * Builders
    NeedsBuilder,
    runBuilder,
    (>>=),
    (>=>),
    NeedsWriter,
    NeedsWriter',

    -- * Mixing Needs together
    concatNeeds,
    applyNeeds,

    -- * Internal
    getCursor,
    getOffset,
    getOrigin,
    writeStorable,

    -- * Dangerous
    unsafeWriteTag,
    unsafeCastNeeds,
    guardRealloc,
    unsafeShiftNeedsPtr,
) where

import qualified Control.Functor.Linear as L
import Data.ByteString.Internal
import Data.Int
import Data.Kind
import qualified Data.Num.Linear as L
import qualified Data.Ord.Linear as L
import Data.Packed.Internal
import Data.Packed.Packed
import Data.Packed.Utils ((:++:))
import Data.Unrestricted.Linear
import Foreign (Storable (..))
import GHC.Exts
import GHC.ForeignPtr
import GHC.IO (IO (..))
import qualified System.IO.Linear as L
import Unsafe.Linear
import Prelude hiding ((>>=))

-- | A buffer where packed values can be written
-- The order to write these values is defined by the 'l' type list
--
-- If 'p' is an empty list, then a value of type 't' can be extracted from that buffer.
-- (See the signature of 'runReader')
data Needs (p :: [Type]) (t :: [Type])
    = Needs
        {-# UNPACK #-} !(MutableByteArray# RealWorld)
        {-# UNPACK #-} !Addr#
        {-# UNPACK #-} !Int

{-# INLINE getCursor #-}
getCursor :: Needs a b %1 -> (# Addr#, Needs a b #)
getCursor = toLinear (\n@(Needs _ cursor _) -> (# cursor, n #))

{-# INLINE getOffset #-}
getOffset :: Needs a b %1 -> (# Int#, Needs a b #)
getOffset = toLinear (\n@(Needs origin cursor _) -> (# cursor `minusAddr#` mutableByteArrayContents# origin, n #))

{-# INLINE getOrigin #-}
getOrigin :: Needs a b %1 -> (# Addr#, Needs a b #)
getOrigin = toLinear (\n@(Needs origin _ _) -> (# mutableByteArrayContents# origin, n #))

{-# INLINE getSpaceLeft #-}
getSpaceLeft :: Needs a b %1 -> (# Int, Needs a b #)
getSpaceLeft = toLinear (\n@(Needs _ _ spaceLeft) -> (# spaceLeft, n #))

-- | Casts a 'Data.Packed.Needs'
{-# INLINE unsafeCastNeeds #-}
unsafeCastNeeds :: Needs a b %1 -> Needs c d
unsafeCastNeeds (Needs a b c) = Needs a b c

--- Needs Builder

-- | A wrapper around a function that builds a 'Needs'
--
-- 'ps': The type of the expected input of the source 'Needs'
--
-- 'ts': The type of the final packed data of the source 'Needs'
--
-- 'pd': The type of the expected input of the resuling 'Needs'
--
-- 'td': The type of the final packed data of the resulting 'Needs'
--
-- __Note:__ It is an indexed monad.
type NeedsBuilder p1 t1 p2 t2 = Needs p1 t1 %1 -> L.IO (Needs p2 t2)

-- | Similar to 'Prelude.>>='
{-# INLINE (>>=) #-}
(>>=) :: L.IO (Needs p1 t1) %1 -> (Needs p1 t1 %1 -> L.IO (Needs p2 t2)) -> L.IO (Needs p2 t2)
(>>=) a b = L.do
    !x <- a
    !x1 <- b x
    L.return x1

-- | Similar to 'Control.Monad.>=>'
{-# INLINE (>=>) #-}
(>=>) :: (a %1 -> L.IO t) %1 -> (t %1 -> L.IO b) -> a %1 -> L.IO b
(>=>) a b !c = L.do
    !x <- a c
    !x1 <- b x
    L.return x1

-- | Shortcut type for 'NeedsBuilder'\'s that simply write a value to a 'Needs' without changing the final packed type
type NeedsWriter a r t = NeedsBuilder (a ': r) t r t

-- | Shortcut type for 'NeedsBuilder'\'s that simply write multiple values to a 'Needs' without changing the final packed type
type NeedsWriter' a r t = NeedsBuilder (a :++: r) t r t

baseBufferSize :: Int
baseBufferSize = 1000

-- | Runs a 'Data.Packed.Needs.NeedsBuilder' computation. Produces a 'Data.Packed.Packed'
{-# INLINE runBuilder #-}
runBuilder :: NeedsBuilder p1 r '[] r -> Packed r
runBuilder builder = unsafeDupablePerformIO $ do
    srcNeeds <- IO $ \s -> case newAlignedPinnedByteArray# (unInt baseBufferSize) 0# s of
        (# s', byteArray #) -> (# s', Needs byteArray (mutableByteArrayContents# byteArray) baseBufferSize #)
    !finalNeeds <- L.withLinearIO $ L.fmap (toLinear Ur) (builder srcNeeds)
    finish finalNeeds

{-# INLINE finish #-}
finish :: Needs '[] a -> IO (Packed a)
finish (Needs og cursor _) = do
    let !contentLen = cursor `minusAddr#` mutableByteArrayContents# og
    () <- IO $ \s -> case shrinkMutableByteArray# og contentLen s of
        s' -> (# s', () #)
    -- SRC: https://hackage.haskell.org/package/ghc-internal-9.1201.0/docs/src/GHC.Internal.ForeignPtr.html#mallocPlainForeignPtrBytes
    let !fptr = ForeignPtr (mutableByteArrayContents# og) (PlainPtr og)
    -- BS calls this function when alloacting an ForeignPtr, let's do that too
    -- https://hackage-content.haskell.org/package/bytestring-0.12.2.0/docs/src/Data.ByteString.Internal.Type.html#mkDeferredByteString
    !bs <- mkDeferredByteString fptr (I# contentLen)
    return $! unsafeToPacked bs

-- | Appends a 'Data.Needs.Needs'
{-# INLINE concatNeeds #-}
concatNeeds :: Needs p t %1 -> NeedsBuilder '[] t1 p (t1 :++: t)
concatNeeds = toLinear2 appendNeeds

-- | Uses a 'Data.Packed.Needs.Needs' to fill in the required values for the other 'Data.Packed.Needs.Needs'
{-# INLINE applyNeeds #-}
applyNeeds :: Needs '[] t1 %1 -> NeedsBuilder (t1 :++: r) t r t
applyNeeds = toLinear2 appendNeeds

-- | Writes a 'Storable'
{-# INLINE writeStorable #-}
writeStorable :: (Storable a) => a -> NeedsBuilder (a ': r) t r t
writeStorable !a !needs = L.do
    !newNeeds <- guardRealloc (sizeOf a) needs
    let !(# cursor, newNeeds1 #) = getCursor newNeeds
    () <- toLinear (\cursor' -> L.fromSystemIO (poke (Ptr cursor') a)) cursor
    L.return (unsafeShiftNeedsPtr (sizeOf a) newNeeds1)

-- Internal

{-# INLINE guardRealloc #-}
guardRealloc :: Int -> Needs p1 t1 %1 -> L.IO (Needs p1 t1)
guardRealloc neededSpace needs =
    if neededSpace L.> spaceLeft
        then reallocNeeds needs1 neededSpace
        else L.return needs1
  where
    !(# spaceLeft, needs1 #) = getSpaceLeft needs

{-# INLINE reallocNeeds #-}
reallocNeeds :: Needs p t %1 -> Int -> L.IO (Needs p t)
reallocNeeds = toLinear reallocNeeds'

reallocNeeds' :: Needs p t -> Int -> L.IO (Needs p t)
reallocNeeds' (Needs origin cursor spaceLeft) additionalNeededSpace =
    let !cursorOffset = I# $ cursor `minusAddr#` mutableByteArrayContents# origin
        !oldBufferSize = spaceLeft + cursorOffset
        !newBufferSize = 2 * (oldBufferSize `max` additionalNeededSpace)
     in L.fromSystemIO
            ( IO $ \s -> case resizeMutableByteArray# origin (unInt newBufferSize) s of
                (# s', reallocedBA #) ->
                    (#
                        s'
                        , Needs
                            reallocedBA
                            (mutableByteArrayContents# reallocedBA `plusAddr#` unInt cursorOffset)
                            (newBufferSize - cursorOffset)
                    #)
            )

{-# NOINLINE appendNeeds #-}
appendNeeds :: Needs a b -> NeedsBuilder a' b' c d
appendNeeds (Needs srcMa cursor _) dest = L.do
    let !srcLen = cursor `minusAddr#` mutableByteArrayContents# srcMa
    !reallocedDest <- guardRealloc (I# srcLen) dest
    let %1 !(# destOg, reallocedDest1 #) = getOrigin reallocedDest
        %1 !() = toLinear (\destAddr -> case runRW# $ copyAddrToAddr# (mutableByteArrayContents# srcMa) destAddr srcLen of !_ -> ()) destOg
    L.return (unsafeCastNeeds reallocedDest1)

-- Utils

{-# INLINE unsafeWriteTag #-}
unsafeWriteTag :: Int8 -> NeedsBuilder a b a' b
unsafeWriteTag tag needs = writeStorable tag (unsafeCastNeeds needs)

{-# INLINE unsafeShiftNeedsPtr #-}
unsafeShiftNeedsPtr :: Int -> Needs a b %1 -> Needs a' b
unsafeShiftNeedsPtr n (Needs og cursor spaceLeft) =
    Needs og (toLinear2 plusAddr# cursor (unInt n)) (spaceLeft L.- n)