ghc-9.12.1: GHC/Iface/Ext/Binary.hs
{-
Binary serialization for .hie files.
-}
module GHC.Iface.Ext.Binary
( readHieFile
, readHieFileWithVersion
, HieHeader
, writeHieFile
, HieName(..)
, toHieName
, HieFileResult(..)
, hieMagic
, hieNameOcc
)
where
import GHC.Prelude
import GHC.Builtin.Utils
import GHC.Settings.Utils ( maybeRead )
import GHC.Settings.Config ( cProjectVersion )
import GHC.Utils.Binary
import GHC.Data.FastMutInt
import GHC.Data.FastString ( FastString )
import GHC.Iface.Ext.Types
import GHC.Iface.Binary ( putAllTables )
import GHC.Types.Name
import GHC.Types.Name.Cache
import GHC.Types.SrcLoc as SrcLoc
import GHC.Types.Unique
import GHC.Types.Unique.FM
import qualified GHC.Utils.Binary as Binary
import GHC.Utils.Outputable
import GHC.Utils.Panic
import qualified Data.Array as A
import qualified Data.Array.IO as A
import qualified Data.Array.Unsafe as A
import Data.IORef
import Data.ByteString ( ByteString )
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import Data.Word ( Word8, Word32 )
import Control.Monad ( replicateM, when, forM_, foldM )
import System.Directory ( createDirectoryIfMissing )
import System.FilePath ( takeDirectory )
data HieSymbolTable = HieSymbolTable
{ hie_symtab_next :: !FastMutInt
, hie_symtab_map :: !(IORef (UniqFM Name (Int, HieName)))
}
initBinMemSize :: Int
initBinMemSize = 1024*1024
-- | The header for HIE files - Capital ASCII letters \"HIE\".
hieMagic :: [Word8]
hieMagic = [72,73,69]
hieMagicLen :: Int
hieMagicLen = length hieMagic
ghcVersion :: ByteString
ghcVersion = BSC.pack cProjectVersion
putBinLine :: WriteBinHandle -> ByteString -> IO ()
putBinLine bh xs = do
mapM_ (putByte bh) $ BS.unpack xs
putByte bh 10 -- newline char
-- | Write a `HieFile` to the given `FilePath`, with a proper header and
-- symbol tables for `Name`s and `FastString`s.
writeHieFile :: FilePath -> HieFile -> IO ()
writeHieFile hie_file_path hiefile = do
bh0 <- openBinMem initBinMemSize
-- Write the header: hieHeader followed by the
-- hieVersion and the GHC version used to generate this file
mapM_ (putByte bh0) hieMagic
putBinLine bh0 $ BSC.pack $ show hieVersion
putBinLine bh0 $ ghcVersion
(fs_tbl, fs_w) <- initFastStringWriterTable
(name_tbl, name_w) <- initWriteNameTable
let bh = setWriterUserData bh0 $ mkWriterUserData
[ mkSomeBinaryWriter @Name name_w
, mkSomeBinaryWriter @BindingName (simpleBindingNameWriter name_w)
, mkSomeBinaryWriter @FastString fs_w
]
-- Discard number of written elements
-- Order matters! See Note [Order of deduplication tables during iface binary serialisation]
_ <- putAllTables bh [fs_tbl, name_tbl] $ do
put_ bh hiefile
-- and send the result to the file
createDirectoryIfMissing True (takeDirectory hie_file_path)
writeBinMem bh hie_file_path
return ()
initWriteNameTable :: IO (WriterTable, BinaryWriter Name)
initWriteNameTable = do
symtab_next <- newFastMutInt 0
symtab_map <- newIORef emptyUFM
let bin_symtab =
HieSymbolTable
{ hie_symtab_next = symtab_next
, hie_symtab_map = symtab_map
}
let put_symtab bh = do
name_count <- readFastMutInt symtab_next
symtab_map <- readIORef symtab_map
putSymbolTable bh name_count symtab_map
pure name_count
return
( WriterTable
{ putTable = put_symtab
}
, mkWriter $ putName bin_symtab
)
initReadNameTable :: NameCache -> IO (ReaderTable Name)
initReadNameTable cache = do
return $
ReaderTable
{ getTable = \bh -> getSymbolTable bh cache
, mkReaderFromTable = \tbl -> mkReader (getSymTabName tbl)
}
data HieFileResult
= HieFileResult
{ hie_file_result_version :: Integer
, hie_file_result_ghc_version :: ByteString
, hie_file_result :: HieFile
}
type HieHeader = (Integer, ByteString)
-- | Read a `HieFile` from a `FilePath`. Can use
-- an existing `NameCache`. Allows you to specify
-- which versions of hieFile to attempt to read.
-- `Left` case returns the failing header versions.
readHieFileWithVersion :: (HieHeader -> Bool) -> NameCache -> FilePath -> IO (Either HieHeader HieFileResult)
readHieFileWithVersion readVersion name_cache file = do
bh0 <- readBinMem file
(hieVersion, ghcVersion) <- readHieFileHeader file bh0
if readVersion (hieVersion, ghcVersion)
then do
hieFile <- readHieFileContents bh0 name_cache
return $ Right (HieFileResult hieVersion ghcVersion hieFile)
else return $ Left (hieVersion, ghcVersion)
-- | Read a `HieFile` from a `FilePath`. Can use
-- an existing `NameCache`.
readHieFile :: NameCache -> FilePath -> IO HieFileResult
readHieFile name_cache file = do
bh0 <- readBinMem file
(readHieVersion, ghcVersion) <- readHieFileHeader file bh0
-- Check if the versions match
when (readHieVersion /= hieVersion) $
panic $ unwords ["readHieFile: hie file versions don't match for file:"
, file
, "Expected"
, show hieVersion
, "but got", show readHieVersion
]
hieFile <- readHieFileContents bh0 name_cache
return $ HieFileResult hieVersion ghcVersion hieFile
readBinLine :: ReadBinHandle -> IO ByteString
readBinLine bh = BS.pack . reverse <$> loop []
where
loop acc = do
char <- get bh :: IO Word8
if char == 10 -- ASCII newline '\n'
then return acc
else loop (char : acc)
readHieFileHeader :: FilePath -> ReadBinHandle -> IO HieHeader
readHieFileHeader file bh0 = do
-- Read the header
magic <- replicateM hieMagicLen (get bh0)
version <- BSC.unpack <$> readBinLine bh0
case maybeRead version of
Nothing ->
panic $ unwords ["readHieFileHeader: hieVersion isn't an Integer:"
, show version
]
Just readHieVersion -> do
ghcVersion <- readBinLine bh0
-- Check if the header is valid
when (magic /= hieMagic) $
panic $ unwords ["readHieFileHeader: headers don't match for file:"
, file
, "Expected"
, show hieMagic
, "but got", show magic
]
return (readHieVersion, ghcVersion)
readHieFileContents :: ReadBinHandle -> NameCache -> IO HieFile
readHieFileContents bh0 name_cache = do
fsReaderTable <- initFastStringReaderTable
nameReaderTable <- initReadNameTable name_cache
-- read the symbol table so we are capable of reading the actual data
bh1 <-
foldM (\bh tblReader -> tblReader bh) bh0
-- The order of these deserialisation matters!
--
-- See Note [Order of deduplication tables during iface binary serialisation] for details.
[ get_dictionary fsReaderTable
, get_dictionary nameReaderTable
]
-- load the actual data
get bh1
where
get_dictionary tbl bin_handle = do
fsTable <- Binary.forwardGetRel bin_handle (getTable tbl bin_handle)
let
fsReader = mkReaderFromTable tbl fsTable
bhFs = addReaderToUserData fsReader bin_handle
pure bhFs
putSymbolTable :: WriteBinHandle -> Int -> UniqFM Name (Int,HieName) -> IO ()
putSymbolTable bh next_off symtab = do
put_ bh next_off
let names = A.elems (A.array (0,next_off-1) (nonDetEltsUFM symtab))
mapM_ (putHieName bh) names
getSymbolTable :: ReadBinHandle -> NameCache -> IO (SymbolTable Name)
getSymbolTable bh name_cache = do
sz <- get bh
mut_arr <- A.newArray_ (0, sz-1) :: IO (A.IOArray Int Name)
forM_ [0..(sz-1)] $ \i -> do
od_name <- getHieName bh
name <- fromHieName name_cache od_name
A.writeArray mut_arr i name
A.unsafeFreeze mut_arr
getSymTabName :: SymbolTable Name -> ReadBinHandle -> IO Name
getSymTabName st bh = do
i :: Word32 <- get bh
return $ st A.! (fromIntegral i)
putName :: HieSymbolTable -> WriteBinHandle -> Name -> IO ()
putName (HieSymbolTable next ref) bh name = do
symmap <- readIORef ref
case lookupUFM symmap name of
Just (off, ExternalName mod occ (UnhelpfulSpan _))
| isGoodSrcSpan (nameSrcSpan name) -> do
let hieName = ExternalName mod occ (nameSrcSpan name)
writeIORef ref $! addToUFM symmap name (off, hieName)
put_ bh (fromIntegral off :: Word32)
Just (off, LocalName _occ span)
| notLocal (toHieName name) || nameSrcSpan name /= span -> do
writeIORef ref $! addToUFM symmap name (off, toHieName name)
put_ bh (fromIntegral off :: Word32)
Just (off, _) -> put_ bh (fromIntegral off :: Word32)
Nothing -> do
off <- readFastMutInt next
writeFastMutInt next (off+1)
writeIORef ref $! addToUFM symmap name (off, toHieName name)
put_ bh (fromIntegral off :: Word32)
where
notLocal :: HieName -> Bool
notLocal LocalName{} = False
notLocal _ = True
-- ** Converting to and from `HieName`'s
fromHieName :: NameCache -> HieName -> IO Name
fromHieName nc hie_name = do
case hie_name of
ExternalName mod occ span -> updateNameCache nc mod occ $ \cache -> do
case lookupOrigNameCache cache mod occ of
Just name -> pure (cache, name)
Nothing -> do
uniq <- takeUniqFromNameCache nc
let name = mkExternalName uniq mod occ span
new_cache = extendOrigNameCache cache mod occ name
pure (new_cache, name)
LocalName occ span -> do
uniq <- takeUniqFromNameCache nc
-- don't update the NameCache for local names
pure $ mkInternalName uniq occ span
KnownKeyName u -> case lookupKnownKeyName u of
Nothing -> pprPanic "fromHieName:unknown known-key unique"
(ppr u)
Just n -> pure n
-- ** Reading and writing `HieName`'s
putHieName :: WriteBinHandle -> HieName -> IO ()
putHieName bh (ExternalName mod occ span) = do
putByte bh 0
put_ bh (mod, occ, BinSrcSpan span)
putHieName bh (LocalName occName span) = do
putByte bh 1
put_ bh (occName, BinSrcSpan span)
putHieName bh (KnownKeyName uniq) = do
putByte bh 2
put_ bh $ unpkUnique uniq
getHieName :: ReadBinHandle -> IO HieName
getHieName bh = do
t <- getByte bh
case t of
0 -> do
(modu, occ, span) <- get bh
return $ ExternalName modu occ $ unBinSrcSpan span
1 -> do
(occ, span) <- get bh
return $ LocalName occ $ unBinSrcSpan span
2 -> do
(c,i) <- get bh
return $ KnownKeyName $ mkUnique c i
_ -> panic "GHC.Iface.Ext.Binary.getHieName: invalid tag"