packages feed

liquidhaskell-boot-0.9.12.2: src/Language/Haskell/Liquid/GHC/Plugin/Serialisation.hs

{-# LANGUAGE ScopedTypeVariables #-}
module Language.Haskell.Liquid.GHC.Plugin.Serialisation (
      -- * Serialising and deserialising things from/to specs.
        serialiseLiquidLib
      , deserialiseLiquidLib
      , deserialiseLiquidLibFromEPS

      ) where

import qualified Data.Array                               as Array
import           Data.Foldable                            ( asum )

import           Control.Monad

import qualified Data.Binary                             as B
import qualified Data.Binary.Builder                     as Builder
import qualified Data.Binary.Put                         as B
import qualified Data.ByteString.Lazy                    as B
import           Data.Data (Data)
import           Data.Generics (ext0, gmapAccumT)
import           Data.HashMap.Strict                     as M
import           Data.Maybe                               ( listToMaybe )
import           Data.Word                               (Word8)

import qualified Liquid.GHC.API as GHC
import           Language.Haskell.Liquid.GHC.Plugin.Types (LiquidLib)
import           Language.Haskell.Liquid.Types.Names


--
-- Serialising and deserialising Specs
--

getLiquidLibBytesFromEPS
  :: GHC.Module
  -> GHC.ExternalPackageState
  -> Maybe LiquidLibBytes
getLiquidLibBytesFromEPS thisModule eps = extractFromEps
  where
    extractFromEps :: Maybe LiquidLibBytes
    extractFromEps = listToMaybe $ GHC.findAnns LiquidLibBytes (GHC.eps_ann_env eps) (GHC.ModuleTarget thisModule)

getLiquidLibBytes :: GHC.Module
                        -> GHC.ExternalPackageState
                        -> GHC.HomePackageTable
                        -> Maybe LiquidLibBytes
getLiquidLibBytes thisModule eps hpt =
    asum [extractFromHpt, getLiquidLibBytesFromEPS thisModule eps]
  where
    extractFromHpt :: Maybe LiquidLibBytes
    extractFromHpt = do
      modInfo <- GHC.lookupHpt hpt (GHC.moduleName thisModule)
      guard (thisModule == (GHC.mi_module . GHC.hm_iface $ modInfo))
      xs <- mapM (GHC.fromSerialized LiquidLibBytes . GHC.ifAnnotatedValue) (GHC.mi_anns . GHC.hm_iface $ modInfo)
      listToMaybe xs

newtype LiquidLibBytes = LiquidLibBytes { unLiquidLibBytes :: [Word8] }

-- | Serialise a 'LiquidLib', removing the termination checks from the target.
serialiseLiquidLib :: LiquidLib -> GHC.Module -> IO GHC.Annotation
serialiseLiquidLib lib thisModule = do
    bs <- encodeLiquidLib lib
    return $ GHC.Annotation
      (GHC.ModuleTarget thisModule)
      (GHC.toSerialized unLiquidLibBytes (LiquidLibBytes $ B.unpack bs))

deserialiseLiquidLib
  :: GHC.Module
  -> GHC.ExternalPackageState
  -> GHC.HomePackageTable
  -> GHC.NameCache
  -> IO (Maybe LiquidLib)
deserialiseLiquidLib thisModule eps hpt nameCache = do
    let mlibbs = getLiquidLibBytes thisModule eps hpt
    case mlibbs of
      Just (LiquidLibBytes ws) -> do
        let bs = B.pack ws
        Just <$> decodeLiquidLib nameCache bs
      _ -> return Nothing

deserialiseLiquidLibFromEPS
  :: GHC.Module
  -> GHC.ExternalPackageState
  -> GHC.NameCache
  -> IO (Maybe LiquidLib)
deserialiseLiquidLibFromEPS thisModule eps nameCache = do
    let mlibbs = getLiquidLibBytesFromEPS thisModule eps
    case mlibbs of
      Just (LiquidLibBytes ws) -> do
        let bs = B.pack ws
        Just <$> decodeLiquidLib nameCache bs
      _ -> return Nothing

encodeLiquidLib :: LiquidLib -> IO B.ByteString
encodeLiquidLib lib0 = do
    let (lib1, ns) = collectLHNames lib0
    bh <- GHC.openBinMem (1024*1024)
    GHC.putWithUserData GHC.QuietBinIFace GHC.SafeExtraCompression bh ns
    GHC.withBinBuffer bh $ \bs ->
      return $ Builder.toLazyByteString $ B.execPut (B.put lib1) <> Builder.fromByteString bs

decodeLiquidLib :: GHC.NameCache -> B.ByteString -> IO LiquidLib
decodeLiquidLib nameCache bs0 = do
    case B.decodeOrFail bs0 of
      Left (_, _, err) -> error $ "decodeLiquidLib: decodeOrFail: " ++ err
      Right (bs1, _, lib) -> do
        bh <- GHC.unsafeUnpackBinBuffer $ B.toStrict bs1
        ns <- GHC.getWithUserData nameCache bh
        let n = fromIntegral $ length ns
            arr = Array.listArray (0, n - 1) ns
        return $ mapLHNames (resolveLHNameIndex arr) lib
  where
    resolveLHNameIndex :: Array.Array Word LHResolvedName -> LHName -> LHName
    resolveLHNameIndex arr lhname =
      case getLHNameResolved lhname of
        LHRIndex i ->
          if i <= snd (Array.bounds arr) then
            makeResolvedLHName (arr Array.! i) (getLHNameSymbol lhname)
          else
            error $ "decodeLiquidLib: index out of bounds: " ++ show (i, Array.bounds arr)
        _ ->
          lhname

newtype AccF a b = AccF { unAccF :: a -> b -> (a, b) }

collectLHNames :: Data a => a -> (a, [LHResolvedName])
collectLHNames t =
    let ((_, _, xs), t') = go (0, M.empty, []) t
     in (t', reverse xs)
  where
    go
      :: Data a
      => (Word, M.HashMap LHResolvedName Word, [LHResolvedName])
      -> a
      -> ((Word, M.HashMap LHResolvedName Word, [LHResolvedName]), a)
    go = gmapAccumT $ unAccF $ AccF go `ext0` AccF collectName

    collectName acc@(sz, m, xs) n = case M.lookup n m of
      Just i -> (acc, LHRIndex i)
      Nothing -> ((sz + 1, M.insert n sz m, n : xs), LHRIndex sz)