{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
{-# LANGUAGE GHCForeignImportPrim, ForeignFunctionInterface, 
             UnliftedFFITypes #-}

{-| 

Module      : GHC.Packing
Copyright   : (c) Jost Berthold, 2010-2015,
License     : BSD3
Maintainer  : jost.berthold@gmail.com
Stability   : experimental
Portability : no (depends on GHC internals)

= Wrapper module for the foreign primitive operations

-}

module GHC.Packing.Core
    ( trySerialize, trySerializeWith, deserialize
    ) where

import GHC.Packing.Type
import GHC.Packing.PackException

import GHC.Exts
import GHC.Prim
import Control.Monad.Primitive
import Data.Primitive.ByteArray

import Control.Exception(throw)

-- the entire package won't support GHC < 7.8
#if __GLASGOW_HASKELL__ < 708
#error This module assumes GHC-7.8 or above
#endif

-- | Serialises its argument (in current evaluation state, as a thunk).
-- May block if the argument captures (blackhole'd) data under evaluation,
-- may throw 'PackException's to signal errors.
-- This version uses a default buffer of 10MB (see 'trySerializeWith'
-- for a version with flexible buffer size).
trySerialize :: a -> IO (Serialized a) -- throws PackException (RTS)
trySerialize x = trySerializeWith x defaultBufSize

-- | default buffer size used by trySerialize
defaultBufSize :: Int
defaultBufSize = 10 * 2^20 -- 10 MB

-- | Extended serialisation interface: Allocates a buffer of given size (in
-- bytes), serialises data into it, then truncates the buffer to the
-- required size before returning it (as @'Serialized' a@)
trySerializeWith :: a -> Int -> IO (Serialized a) -- using instance PrimMonad IO
trySerializeWith dat bufsize
    = do buf <- newByteArray bufsize
         size <- trySerializeInto buf dat
         buf' <- truncate' buf size
         ByteArray b# <- unsafeFreezeByteArray buf'
         return (Serialized { packetData = b# })

-- | core routine. Packs x into mutable byte array buf, returns size
-- of packed x in buf
trySerializeInto :: MutableByteArray RealWorld -> a -> IO Int
trySerializeInto (MutableByteArray buf# ) x 
    = primitive (tryPack (unsafeCoerce# x :: Any) buf# )

-- | calls primitive, decodes/throws errors + wraps Int# size into Int
tryPack :: Any -> MutableByteArray# s
        -> State# s -> (# State# s , Int #)
tryPack x# buf# s = case tryPack# x# buf# s of
                      (# s', 0#, size# #) -> (# s', I# size# #)
                      (# s', e#,   0#  #) 
                          | isBHExc e# -> repack s'
                          | otherwise  -> (# s', throw (decodeEx e#) #)
    where -- packing blocked, eval the blocking closure that we found
          -- (i.e. block on it) and re-pack afterwards. The first
          -- StgWord of the ByteArray contains the address (written by
          -- the packing routine, see BLACKHOLE case in packClosure).
      repack s = case readAddrArray# buf# 0# s of
                   (# s', bh #) -> case (addrToAny# bh) of -- or seq it?
                                     _ -> tryPack x# buf# s'

-- | serialisation primitive, implemented in C. Returns: a
-- status/error code and size used inside the array
foreign import prim "stg_tryPack" tryPack#
    :: Any -> MutableByteArray# s -> State# s -> (# State# s, Int#, Int# #)

-- GHC-7.8 does not have an in-place shrink operation for MutableByteArrays
-- (added in GHC-7.9 on August 16, 2014)
-- GHC-7.9, August 2014 :: MutableByteArray# s -> Int# -> State# s -> State# s
-- with this one available, tryPack could do the work
-- for GHC-7.8, we copy
truncate' :: PrimMonad m => MutableByteArray (PrimState m) -> Int -> m (MutableByteArray (PrimState m))
truncate' b size 
    = if sizeofMutableByteArray b < size
      then throw P_NOBUFFER -- XXX other error?
      else do b' <- newByteArray size
              copyMutableByteArray b' 0 b 0 size
              return b'

--------------------------------------------------------

-- | Deserialisation function. May throw @'PackException'@ @'P_GARBLED'@
deserialize :: Serialized a -> IO a
deserialize p = primitive (deser (packetData p))

deser :: ByteArray# -> State# s -> (# State# s, a #)
deser buf s = case unpack# buf s of
                (# s', 0#, x #) -> (# s', x #)
                (# s', n#, _ #) -> (# s', throw (decodeEx n#) #)

foreign import prim "stg_unpack" unpack# :: ByteArray# -> State# s -> (# State# s, Int#, a #)