diff options
Diffstat (limited to 'compiler/GHC/StgToJS/Object.hs')
-rw-r--r-- | compiler/GHC/StgToJS/Object.hs | 845 |
1 files changed, 845 insertions, 0 deletions
diff --git a/compiler/GHC/StgToJS/Object.hs b/compiler/GHC/StgToJS/Object.hs new file mode 100644 index 0000000000..bb986926b1 --- /dev/null +++ b/compiler/GHC/StgToJS/Object.hs @@ -0,0 +1,845 @@ +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE Rank2Types #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +-- | Serialization/deserialization of binary .o files for the JavaScript backend +-- +-- The .o files contain dependency information and generated code. +-- +-- All strings are mapped to a central string table, which helps reduce +-- file size and gives us efficient hash consing on read +-- +-- Binary intermediate JavaScript object files: +-- serialized [Text] -> ([ClosureInfo], JStat) blocks +-- +-- file layout: +-- - header ["GHCJSOBJ", length of symbol table, length of dependencies, length of index] +-- - compiler version tag +-- - symbol table +-- - dependency info +-- - closureinfo index +-- - closureinfo data (offsets described by index) +-- +module GHC.StgToJS.Object + ( object + , object' + , readDepsFile + , readDepsFileEither + , hReadDeps + , hReadDepsEither + , readDeps, readDepsMaybe + , readObjectFile + , readObjectFileKeys + , readObject + , readObjectKeys + , serializeStat + , emptySymbolTable + , isGlobalUnit + , isExportsUnit -- XXX verify that this is used + -- XXX probably should instead do something that just inspects the header instead of exporting it + , Header(..), getHeader, moduleNameTag + , SymbolTable + , ObjUnit (..) + , Deps (..), BlockDeps (..) + , ExpFun (..), ExportedFun (..) + , versionTag, versionTagLength + ) +where + +import GHC.Prelude + +import Control.Exception (bracket) +import Control.Monad +import Control.Monad.Trans.Class +import Control.Monad.Trans.Reader +import qualified Control.Monad.Trans.State as St + +import Data.Array +import Data.Monoid +import qualified Data.Binary as DB +import qualified Data.Binary.Get as DB +import qualified Data.Binary.Put as DB +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as B +import Data.ByteString.Lazy (ByteString) +import qualified Data.ByteString.Lazy.Char8 as C8 (pack, unpack) +import qualified Data.ByteString.Short as SBS +import Data.Function (on) +import Data.Int +import Data.IntSet (IntSet) +import qualified Data.IntSet as IS +import Data.List (sortBy) +import Data.Map (Map) +import qualified Data.Map as M +import Data.Maybe (catMaybes) +import Data.Set (Set) +import qualified Data.Set as S +import Data.Word +import Data.Char (isSpace) + +import GHC.Generics +import GHC.Settings.Constants (hiVersion) + +import System.IO (openBinaryFile, withBinaryFile, Handle, + hClose, hSeek, SeekMode(..), IOMode(..) ) + +import GHC.JS.Syntax +import GHC.StgToJS.Types + +import GHC.Unit.Module + +import GHC.Data.FastString +import GHC.Data.ShortText as ST + +import GHC.Utils.Misc + +data Header = Header + { hdrModuleName :: !BS.ByteString + , hdrSymbsLen :: !Int64 + , hdrDepsLen :: !Int64 + , hdrIdxLen :: !Int64 + } deriving (Eq, Ord, Show) + +-- | dependencies for a single module +data Deps = Deps + { depsModule :: !Module -- ^ module + , depsRequired :: !IntSet -- ^ blocks that always need to be linked when this object is loaded (e.g. everything that contains initializer code or foreign exports) + , depsHaskellExported :: !(Map ExportedFun Int) -- ^ exported Haskell functions -> block + , depsBlocks :: !(Array Int BlockDeps) -- ^ info about each block + } deriving (Generic) + +data BlockDeps = BlockDeps + { blockBlockDeps :: [Int] -- ^ dependencies on blocks in this object + , blockFunDeps :: [ExportedFun] -- ^ dependencies on exported symbols in other objects + -- , blockForeignExported :: [ExpFun] + -- , blockForeignImported :: [ForeignRef] + } deriving (Generic) + +data ExpFun = ExpFun + { isIO :: !Bool + , args :: [JSFFIType] + , result :: !JSFFIType + } deriving (Eq, Ord, Show) + +trim :: String -> String +trim = let f = dropWhile isSpace . reverse in f . f + +{- | we use the convention that the first unit (0) is a module-global + unit that's always included when something from the module + is loaded. everything in a module implicitly depends on the + global block. the global unit itself can't have dependencies + -} +isGlobalUnit :: Int -> Bool +isGlobalUnit n = n == 0 + +-- fixme document, exports unit is always linked +isExportsUnit :: Int -> Bool +isExportsUnit n = n == 1 + +data JSFFIType + = Int8Type + | Int16Type + | Int32Type + | Int64Type + | Word8Type + | Word16Type + | Word32Type + | Word64Type + | DoubleType + | ByteArrayType + | PtrType + | RefType + deriving (Show, Ord, Eq, Enum) + +data ExportedFun = ExportedFun + { funModule :: !Module + , funSymbol :: !ShortText + } deriving (Eq, Ord) + +-- we need to store the size separately, since getting a HashMap's size is O(n) +data SymbolTable + = SymbolTable !Int !(Map ShortText Int) + deriving (Show) + +emptySymbolTable :: SymbolTable +emptySymbolTable = SymbolTable 0 M.empty + +insertSymbol :: ShortText -> SymbolTable -> (SymbolTable, Int) +insertSymbol s st@(SymbolTable n t) = + case M.lookup s t of + Just k -> (st, k) + Nothing -> (SymbolTable (n+1) (M.insert s n t), n) + +data ObjEnv = ObjEnv + { oeSymbols :: SymbolTableR + , oeName :: String + } + +data SymbolTableR = SymbolTableR + { strText :: Array Int ShortText + , strString :: Array Int String + } + +type PutSM = St.StateT SymbolTable DB.PutM -- FIXME: StateT isn't strict enough apparently +type PutS = PutSM () +type GetS = ReaderT ObjEnv DB.Get + +class Objectable a where + put :: a -> PutS + get :: GetS a + putList :: [a] -> PutS + putList = putListOf put + getList :: GetS [a] + getList = getListOf get + +runGetS :: HasDebugCallStack => String -> SymbolTableR -> GetS a -> ByteString -> a +runGetS name st m bs = DB.runGet (runReaderT m (ObjEnv st name)) bs + +runPutS :: SymbolTable -> PutS -> (SymbolTable, ByteString) +runPutS st ps = DB.runPutM (St.execStateT ps st) + +unexpected :: String -> GetS a +unexpected err = ask >>= \e -> + error (oeName e ++ ": " ++ err) + +-- one toplevel block in the object file +data ObjUnit = ObjUnit + { oiSymbols :: [ShortText] -- toplevel symbols (stored in index) + , oiClInfo :: [ClosureInfo] -- closure information of all closures in block + , oiStatic :: [StaticInfo] -- static closure data + , oiStat :: JStat -- the code + , oiRaw :: ShortText -- raw JS code + , oiFExports :: [ExpFun] + , oiFImports :: [ForeignJSRef] + } + +-- | build an object file +object :: ModuleName -- ^ the module name + -> Deps -- ^ the dependencies + -> [ObjUnit] -- ^ units, the first unit is the module-global one + -> ByteString -- ^ serialized object +object mname ds units = object' mname symbs ds xs + where + (xs, symbs) = go emptySymbolTable units + go st0 (ObjUnit sy cl si st str fe fi : ys) = + let (st1, bs) = serializeStat st0 cl si st str fe fi + (bss, st2) = go st1 ys + in ((sy,B.fromChunks [bs]):bss, st2) + go st0 [] = ([], st0) + +serializeStat :: SymbolTable + -> [ClosureInfo] + -> [StaticInfo] + -> JStat + -> ShortText + -> [ExpFun] + -> [ForeignJSRef] + -> (SymbolTable, BS.ByteString) +serializeStat st ci si s sraw fe fi = + let (st', bs) = runPutS st $ do + put ci + put si + put s + put sraw + put fe + put fi + bs' = B.toStrict bs + in (st', bs') + +-- tag to store the module name in the object file +moduleNameTag :: ModuleName -> BS.ByteString +moduleNameTag (ModuleName fs) = case compare len moduleNameLength of + EQ -> tag + LT -> tag <> BS.replicate (moduleNameLength - len) 0 -- pad with 0s + GT -> BS.drop (len - moduleNameLength) tag -- take only the ending chars + where + !tag = SBS.fromShort (fs_sbs fs) + !len = n_chars fs + +object' + :: ModuleName -- ^ module + -> SymbolTable -- ^ final symbol table + -> Deps -- ^ dependencies + -> [([ShortText],ByteString)] -- ^ serialized units and their exported symbols, the first unit is module-global + -> ByteString +object' mod_name st0 deps0 os = hdr <> symbs <> deps1 <> idx <> mconcat (map snd os) + where + hdr = putHeader (Header (moduleNameTag mod_name) (bl symbs) (bl deps1) (bl idx)) + bl = fromIntegral . B.length + deps1 = putDepsSection deps0 + (sti, idx) = putIndex st0 os + symbs = putSymbolTable sti + +putIndex :: SymbolTable -> [([ShortText], ByteString)] -> (SymbolTable, ByteString) +putIndex st xs = runPutS st (put $ zip symbols offsets) + where + (symbols, values) = unzip xs + offsets = scanl (+) 0 (map B.length values) + +getIndex :: HasDebugCallStack => String -> SymbolTableR -> ByteString -> [([ShortText], Int64)] +getIndex name st bs = runGetS name st get bs + +putDeps :: SymbolTable -> Deps -> (SymbolTable, ByteString) +putDeps st deps = runPutS st (put deps) + +getDeps :: HasDebugCallStack => String -> SymbolTableR -> ByteString -> Deps +getDeps name st bs = runGetS name st get bs + +toI32 :: Int -> Int32 +toI32 = fromIntegral + +fromI32 :: Int32 -> Int +fromI32 = fromIntegral + +putDepsSection :: Deps -> ByteString +putDepsSection deps = + let (st, depsbs) = putDeps emptySymbolTable deps + stbs = putSymbolTable st + in DB.runPut (DB.putWord32le (fromIntegral $ B.length stbs)) <> stbs <> depsbs + +getDepsSection :: HasDebugCallStack => String -> ByteString -> Deps +getDepsSection name bs = + let symbsLen = fromIntegral $ DB.runGet DB.getWord32le bs + symbs = getSymbolTable (B.drop 4 bs) + in getDeps name symbs (B.drop (4+symbsLen) bs) + +instance Objectable Deps where + put (Deps m r e b) = do + put m + put (map toI32 $ IS.toList r) + put (map (\(x,y) -> (x, toI32 y)) $ M.toList e) + put (elems b) + get = Deps <$> get + <*> (IS.fromList . map fromI32 <$> get) + <*> (M.fromList . map (\(x,y) -> (x, fromI32 y)) <$> get) + <*> ((\xs -> listArray (0, length xs - 1) xs) <$> get) + +instance Objectable BlockDeps where + put (BlockDeps bbd bfd) = put bbd >> put bfd + get = BlockDeps <$> get <*> get + +instance Objectable ForeignJSRef where + put (ForeignJSRef span pat safety cconv arg_tys res_ty) = + put span >> put pat >> putEnum safety >> putEnum cconv >> put arg_tys >> put res_ty + get = ForeignJSRef <$> get <*> get <*> getEnum <*> getEnum <*> get <*> get + +instance Objectable ExpFun where + put (ExpFun isIO args res) = put isIO >> put args >> put res + get = ExpFun <$> get <*> get <*> get + +-- | reads only the part necessary to get the dependencies +-- so it's potentially more efficient than readDeps <$> B.readFile file +readDepsFile :: FilePath -> IO Deps +readDepsFile file = withBinaryFile file ReadMode (hReadDeps file) + +readDepsFileEither :: FilePath -> IO (Either String Deps) +readDepsFileEither file = withBinaryFile file ReadMode (hReadDepsEither file) + +hReadDeps :: String -> Handle -> IO Deps +hReadDeps name h = do + res <- hReadDepsEither name h + case res of + Left err -> error ("hReadDeps: not a valid GHCJS object: " ++ name ++ "\n " ++ err) + Right deps -> pure deps + +hReadDepsEither :: String -> Handle -> IO (Either String Deps) +hReadDepsEither name h = do + mhdr <- getHeader <$> B.hGet h headerLength + case mhdr of + Left err -> pure (Left err) + Right hdr -> do + hSeek h RelativeSeek (fromIntegral $ hdrSymbsLen hdr) + Right . getDepsSection name <$> B.hGet h (fromIntegral $ hdrDepsLen hdr) + +readDepsEither :: String -> ByteString -> Either String Deps +readDepsEither name bs = + case getHeader bs of + Left err -> Left err + Right hdr -> + let depsStart = fromIntegral headerLength + fromIntegral (hdrSymbsLen hdr) + in Right $ getDepsSection name (B.drop depsStart bs) + + +-- | call with contents of the file +readDeps :: String -> ByteString -> Deps +readDeps name bs = + case readDepsEither name bs of + Left err -> error ("readDeps: not a valid GHCJS object: " ++ name ++ "\n " ++ err) + Right deps -> deps + +readDepsMaybe :: String -> ByteString -> Maybe Deps +readDepsMaybe name bs = either (const Nothing) Just (readDepsEither name bs) + +-- | extract the linkable units from an object file +readObjectFile :: FilePath -> IO [ObjUnit] +readObjectFile = readObjectFileKeys (\_ _ -> True) + +readObjectFileKeys :: (Int -> [ShortText] -> Bool) -> FilePath -> IO [ObjUnit] +readObjectFileKeys p file = bracket (openBinaryFile file ReadMode) hClose $ \h -> do + mhdr <- getHeader <$> B.hGet h headerLength + case mhdr of + Left err -> error ("readObjectFileKeys: not a valid GHCJS object: " ++ file ++ "\n " ++ err) + Right hdr -> do + bss <- B.hGet h (fromIntegral $ hdrSymbsLen hdr) + hSeek h RelativeSeek (fromIntegral $ hdrDepsLen hdr) + bsi <- B.fromStrict <$> BS.hGetContents h + return $ readObjectKeys' file p (getSymbolTable bss) bsi (B.drop (fromIntegral $ hdrIdxLen hdr) bsi) + +readObject :: String -> ByteString -> [ObjUnit] +readObject name = readObjectKeys name (\_ _ -> True) + +readObjectKeys :: HasDebugCallStack => String -> (Int -> [ShortText] -> Bool) -> ByteString -> [ObjUnit] +readObjectKeys name p bs = + case getHeader bs of + Left err -> error ("readObjectKeys: not a valid GHCJS object: " ++ name ++ "\n " ++ err) + Right hdr -> + let bssymbs = B.drop (fromIntegral headerLength) bs + bsidx = B.drop (fromIntegral $ hdrSymbsLen hdr + hdrDepsLen hdr) bssymbs + bsobjs = B.drop (fromIntegral $ hdrIdxLen hdr) bsidx + in readObjectKeys' name p (getSymbolTable bssymbs) bsidx bsobjs + +readObjectKeys' :: HasDebugCallStack + => String + -> (Int -> [ShortText] -> Bool) + -> SymbolTableR + -> ByteString + -> ByteString + -> [ObjUnit] +readObjectKeys' name p st bsidx bsobjs = catMaybes (zipWith readObj [0..] idx) + where + idx = getIndex name st bsidx + readObj n (x,off) + | p n x = let (ci, si, s, sraw, fe, fi) = runGetS name st ((,,,,,) <$> get <*> get <*> get <*> get <*> get <*> get) (B.drop off bsobjs) + in Just (ObjUnit x ci si s sraw fe fi) + | otherwise = Nothing + +getSymbolTable :: HasDebugCallStack => ByteString -> SymbolTableR +getSymbolTable bs = SymbolTableR (listArray (0,n-1) xs) (listArray (0,n-1) (map ST.unpack xs)) + where + (n,xs) = DB.runGet getter bs + getter :: DB.Get (Int, [ShortText]) + getter = do + l <- DB.getWord32le + let l' = fromIntegral l + (l',) <$> replicateM l' DB.get + +putSymbolTable :: SymbolTable -> ByteString +putSymbolTable (SymbolTable _ hm) = st + where + st = DB.runPut $ do + DB.putWord32le (fromIntegral $ length xs) + mapM_ DB.put xs + -- fixme: this is a workaround for some weird issue sometimes causing zero-length + -- strings when using the Data.Text instance directly + -- mapM_ (DB.put . TE.encodeUtf8) xs + xs :: [ShortText] + xs = map fst . sortBy (compare `on` snd) . M.toList $ hm + +headerLength :: Int +headerLength = 32 + versionTagLength + moduleNameLength + +-- human readable version string in object +versionTag :: ByteString +versionTag = B.take 32 . C8.pack $ show hiVersion ++ replicate versionTagLength ' ' + +versionTagLength :: Int +versionTagLength = 32 + +-- last part of the module name, to disambiguate files +moduleNameLength :: Int +moduleNameLength = 128 + +getHeader :: HasDebugCallStack => ByteString -> Either String Header +getHeader bs + | B.length bs < fromIntegral headerLength = Left "not enough input, file truncated?" + | magic /= "GHCJSOBJ" = Left $ "magic number incorrect, not a JavaScript .o file?" + | tag /= versionTag = Left $ "incorrect version, expected " ++ show hiVersion ++ + " but got " ++ (trim . C8.unpack $ tag) + | otherwise = Right (Header mn sl dl il) + where + g = fromIntegral <$> DB.getWord64le + (magic, tag, mn, sl, dl, il) = DB.runGet ((,,,,,) <$> DB.getByteString 8 + <*> DB.getLazyByteString (fromIntegral versionTagLength) + <*> DB.getByteString (fromIntegral moduleNameLength) + <*> g + <*> g + <*> g + ) bs + +putHeader :: Header -> ByteString +putHeader (Header mn sl dl il) = DB.runPut $ do + DB.putByteString "GHCJSOBJ" + DB.putLazyByteString versionTag + DB.putByteString mn + mapM_ (DB.putWord64le . fromIntegral) [sl, dl, il] + +tag :: Word8 -> PutS +tag x = lift (DB.putWord8 x) + +getTag :: GetS Word8 +getTag = lift DB.getWord8 + +instance (Objectable a, Objectable b) => Objectable (a, b) where + put (x, y) = put x >> put y + get = (,) <$> get <*> get + +instance Objectable a => Objectable [a] where + put = putList + get = getList + +instance Objectable Char where + put = lift . DB.putWord32le . fromIntegral . fromEnum + get = toEnum . fromIntegral <$> lift DB.getWord32le + putList = put . ST.pack + getList = do + st <- oeSymbols <$> ask + n <- lift DB.getWord32le + return (strString st ! fromIntegral n) + +putListOf :: (a -> PutS) -> [a] -> PutS +putListOf p xs = do + lift (DB.putWord32le (fromIntegral $ length xs)) + mapM_ p xs + +getListOf :: GetS a -> GetS [a] +getListOf g = do + l <- lift DB.getWord32le + replicateM (fromIntegral l) g + +instance (Ord k, Objectable k, Objectable v) => Objectable (Map k v) where + put = put . M.toList + get = M.fromList <$> get + +instance (Ord a, Objectable a) => Objectable (Set a) where + put = put . S.toList + get = S.fromList <$> get + +instance Objectable Word64 where + put = lift . DB.putWord64le + get = lift DB.getWord64le + +instance Objectable Int64 where + put = lift . DB.putWord64le . fromIntegral + get = fromIntegral <$> lift DB.getWord64le + +instance Objectable Word32 where + put = lift . DB.putWord32le + get = lift DB.getWord32le + +instance Objectable Int32 where + put = lift . DB.putWord32le . fromIntegral + get = fromIntegral <$> lift DB.getWord32le + +instance Objectable a => Objectable (Maybe a) where + put Nothing = tag 1 + put (Just x) = tag 2 >> put x + get = getTag >>= \case + 1 -> pure Nothing + 2 -> Just <$> get + n -> unexpected ("Objectable get Maybe: invalid tag: " ++ show n) + +instance Objectable ShortText where + put t = do + symbols <- St.get + let (symbols', n) = insertSymbol t symbols + St.put symbols' + lift (DB.putWord32le $ fromIntegral n) + get = do + st <- oeSymbols <$> ask + n <- lift DB.getWord32le + return (strText st ! fromIntegral n) + +instance Objectable JStat where + put (DeclStat i) = tag 1 >> put i + put (ReturnStat e) = tag 2 >> put e + put (IfStat e s1 s2) = tag 3 >> put e >> put s1 >> put s2 + put (WhileStat b e s) = tag 4 >> put b >> put e >> put s + put (ForInStat b i e s) = tag 5 >> put b >> put i >> put e >> put s + put (SwitchStat e ss s) = tag 6 >> put e >> put ss >> put s + put (TryStat s1 i s2 s3) = tag 7 >> put s1 >> put i >> put s2 >> put s3 + put (BlockStat xs) = tag 8 >> put xs + put (ApplStat e es) = tag 9 >> put e >> put es + put (UOpStat o e) = tag 10 >> put o >> put e + put (AssignStat e1 e2) = tag 11 >> put e1 >> put e2 + put (UnsatBlock {}) = error "put JStat: UnsatBlock" + put (LabelStat l s) = tag 12 >> put l >> put s + put (BreakStat ml) = tag 13 >> put ml + put (ContinueStat ml) = tag 14 >> put ml + get = getTag >>= \case + 1 -> DeclStat <$> get + 2 -> ReturnStat <$> get + 3 -> IfStat <$> get <*> get <*> get + 4 -> WhileStat <$> get <*> get <*> get + 5 -> ForInStat <$> get <*> get <*> get <*> get + 6 -> SwitchStat <$> get <*> get <*> get + 7 -> TryStat <$> get <*> get <*> get <*> get + 8 -> BlockStat <$> get + 9 -> ApplStat <$> get <*> get + 10 -> UOpStat <$> get <*> get + 11 -> AssignStat <$> get <*> get + 12 -> LabelStat <$> get <*> get + 13 -> BreakStat <$> get + 14 -> ContinueStat <$> get + n -> unexpected ("Objectable get JStat: invalid tag: " ++ show n) + +instance Objectable JExpr where + put (ValExpr v) = tag 1 >> put v + put (SelExpr e i) = tag 2 >> put e >> put i + put (IdxExpr e1 e2) = tag 3 >> put e1 >> put e2 + put (InfixExpr o e1 e2) = tag 4 >> put o >> put e1 >> put e2 + put (UOpExpr o e) = tag 5 >> put o >> put e + put (IfExpr e1 e2 e3) = tag 6 >> put e1 >> put e2 >> put e3 + put (ApplExpr e es) = tag 7 >> put e >> put es + put (UnsatExpr {}) = error "put JExpr: UnsatExpr" + get = getTag >>= \case + 1 -> ValExpr <$> get + 2 -> SelExpr <$> get <*> get + 3 -> IdxExpr <$> get <*> get + 4 -> InfixExpr <$> get <*> get <*> get + 5 -> UOpExpr <$> get <*> get + 6 -> IfExpr <$> get <*> get <*> get + 7 -> ApplExpr <$> get <*> get + n -> unexpected ("Objectable get JExpr: invalid tag: " ++ show n) + +instance Objectable JVal where + put (JVar i) = tag 1 >> put i + put (JList es) = tag 2 >> put es + put (JDouble d) = tag 3 >> put d + put (JInt i) = tag 4 >> put i + put (JStr xs) = tag 5 >> put xs + put (JRegEx xs) = tag 6 >> put xs + put (JHash m) = tag 7 >> put (M.toList m) + put (JFunc is s) = tag 8 >> put is >> put s + put (UnsatVal {}) = error "put JVal: UnsatVal" + get = getTag >>= \case + 1 -> JVar <$> get + 2 -> JList <$> get + 3 -> JDouble <$> get + 4 -> JInt <$> get + 5 -> JStr <$> get + 6 -> JRegEx <$> get + 7 -> JHash . M.fromList <$> get + 8 -> JFunc <$> get <*> get + n -> unexpected ("Objectable get JVal: invalid tag: " ++ show n) + +instance Objectable Ident where + put (TxtI xs) = put xs + get = TxtI <$> get + +instance Objectable Integer where + put = lift . DB.put + get = lift DB.get + +-- we need to preserve NaN and infinities, unfortunately the Binary instance for Double does not do this +instance Objectable SaneDouble where + put (SaneDouble d) + | isNaN d = tag 1 + | isInfinite d && d > 0 = tag 2 + | isInfinite d && d < 0 = tag 3 + | isNegativeZero d = tag 4 + | otherwise = tag 5 >> lift (DB.put d) + get = getTag >>= \case + 1 -> pure $ SaneDouble (0 / 0) + 2 -> pure $ SaneDouble (1 / 0) + 3 -> pure $ SaneDouble ((-1) / 0) + 4 -> pure $ SaneDouble (-0) + 5 -> SaneDouble <$> lift DB.get + n -> unexpected ("Objectable get SaneDouble: invalid tag: " ++ show n) + +instance Objectable ClosureInfo where + put (ClosureInfo v regs name layo typ static) = do + put v >> put regs >> put name >> put layo >> put typ >> put static + get = ClosureInfo <$> get <*> get <*> get <*> get <*> get <*> get + +instance Objectable JSFFIType where + put = putEnum + get = getEnum + +instance Objectable VarType where + put = putEnum + get = getEnum + +instance Objectable CIRegs where + put CIRegsUnknown = tag 1 + put (CIRegs skip types) = tag 2 >> putIW16 skip >> put types + get = getTag >>= \case + 1 -> pure CIRegsUnknown + 2 -> CIRegs <$> getIW16 <*> get + n -> unexpected ("Objectable get CIRegs: invalid tag: " ++ show n) + +instance Objectable JOp where + put = putEnum + get = getEnum + +instance Objectable JUOp where + put = putEnum + get = getEnum + +-- 16 bit sizes should be enough... +instance Objectable CILayout where + put CILayoutVariable = tag 1 + put (CILayoutUnknown size) = tag 2 >> putIW16 size + put (CILayoutFixed size types) = tag 3 >> putIW16 size >> put types + get = getTag >>= \case + 1 -> pure CILayoutVariable + 2 -> CILayoutUnknown <$> getIW16 + 3 -> CILayoutFixed <$> getIW16 <*> get + n -> unexpected ("Objectable get CILayout: invalid tag: " ++ show n) + +instance Objectable CIStatic where + put (CIStaticRefs refs) = tag 1 >> put refs + get = getTag >>= \case + 1 -> CIStaticRefs <$> get + n -> unexpected ("Objectable get CIStatic: invalid tag: " ++ show n) + +instance Objectable CIType where + put (CIFun arity regs) = tag 1 >> putIW16 arity >> putIW16 regs + put CIThunk = tag 2 + put (CICon conTag) = tag 3 >> putIW16 conTag + put CIPap = tag 4 + put CIBlackhole = tag 5 + put CIStackFrame = tag 6 + get = getTag >>= \case + 1 -> CIFun <$> getIW16 <*> getIW16 + 2 -> pure CIThunk + 3 -> CICon <$> getIW16 + 4 -> pure CIPap + 5 -> pure CIBlackhole + 6 -> pure CIStackFrame + n -> unexpected ("Objectable get CIType: invalid tag: " ++ show n) + +-- put an Int as a Word16, little endian. useful for many small values +putIW16 :: Int -> PutS +putIW16 i | i > 65535 || i < 0 = error ("putIW16: out of range: " ++ show i) + | otherwise = lift $ DB.putWord16le (fromIntegral i) + +getIW16 :: GetS Int +getIW16 = lift (fmap fromIntegral DB.getWord16le) + +-- the binary instance stores ints as 64 bit +instance Objectable Int where + put = lift . DB.put + get = lift DB.get + +instance Objectable ExportedFun where + put (ExportedFun modu symb) = put modu >> put symb + get = ExportedFun <$> get <*> get + +instance Objectable Module where + put (Module unit mod_name) = put unit >> put mod_name + get = Module <$> get <*> get + +instance Objectable ModuleName where + put (ModuleName fs) = put fs + get = ModuleName <$> get + +instance Objectable Unit where + put = \case + RealUnit (Definite uid) -> tag 0 >> put uid + VirtUnit uid -> tag 1 >> put uid + HoleUnit -> tag 2 + get = getTag >>= \case + 0 -> (RealUnit . Definite) <$> get + 1 -> VirtUnit <$> get + _ -> pure HoleUnit + +instance Objectable FastString where + put fs = put (unpackFS fs) + get = mkFastString <$> get + +instance Objectable UnitId where + put (UnitId fs) = put fs + get = UnitId <$> get + +instance Objectable InstantiatedUnit where + put indef = do + put (instUnitInstanceOf indef) + put (instUnitInsts indef) + get = mkInstantiatedUnitSorted <$> get <*> get + +putEnum :: Enum a => a -> PutS +putEnum x | n > 65535 = error ("putEnum: out of range: " ++ show n) + | otherwise = putIW16 n + where n = fromEnum x + +getEnum :: Enum a => GetS a +getEnum = toEnum <$> getIW16 + +instance Objectable Bool where + put False = tag 1 + put True = tag 2 + get = getTag >>= \case + 1 -> return False + 2 -> return True + n -> unexpected ("Objectable get Bool: invalid tag: " ++ show n) + +instance Objectable StaticInfo where + put (StaticInfo ident val cc) = put ident >> put val >> put cc + get = StaticInfo <$> get <*> get <*> get + +instance Objectable StaticVal where + put (StaticFun f args) = tag 1 >> put f >> put args + put (StaticThunk t) = tag 2 >> put t + put (StaticUnboxed u) = tag 3 >> put u + put (StaticData dc args) = tag 4 >> put dc >> put args + put (StaticList xs t) = tag 5 >> put xs >> put t + get = getTag >>= \case + 1 -> StaticFun <$> get <*> get + 2 -> StaticThunk <$> get + 3 -> StaticUnboxed <$> get + 4 -> StaticData <$> get <*> get + 5 -> StaticList <$> get <*> get + n -> unexpected ("Objectable get StaticVal: invalid tag " ++ show n) + +instance Objectable StaticUnboxed where + put (StaticUnboxedBool b) = tag 1 >> put b + put (StaticUnboxedInt i) = tag 2 >> put i + put (StaticUnboxedDouble d) = tag 3 >> put d + put (StaticUnboxedString str) = tag 4 >> put str + put (StaticUnboxedStringOffset str) = tag 5 >> put str + get = getTag >>= \case + 1 -> StaticUnboxedBool <$> get + 2 -> StaticUnboxedInt <$> get + 3 -> StaticUnboxedDouble <$> get + 4 -> StaticUnboxedString <$> get + 5 -> StaticUnboxedStringOffset <$> get + n -> unexpected ("Objectable get StaticUnboxed: invalid tag " ++ show n) + +instance Objectable StaticArg where + put (StaticObjArg i) = tag 1 >> put i + put (StaticLitArg p) = tag 2 >> put p + put (StaticConArg c args) = tag 3 >> put c >> put args + get = getTag >>= \case + 1 -> StaticObjArg <$> get + 2 -> StaticLitArg <$> get + 3 -> StaticConArg <$> get <*> get + n -> unexpected ("Objectable get StaticArg: invalid tag " ++ show n) + +instance Objectable StaticLit where + put (BoolLit b) = tag 1 >> put b + put (IntLit i) = tag 2 >> put i + put NullLit = tag 3 + put (DoubleLit d) = tag 4 >> put d + put (StringLit t) = tag 5 >> put t + put (BinLit b) = tag 6 >> put b + put (LabelLit b t) = tag 7 >> put b >> put t + get = getTag >>= \case + 1 -> BoolLit <$> get + 2 -> IntLit <$> get + 3 -> pure NullLit + 4 -> DoubleLit <$> get + 5 -> StringLit <$> get + 6 -> BinLit <$> get + 7 -> LabelLit <$> get <*> get + n -> unexpected ("Objectable get StaticLit: invalid tag " ++ show n) + +instance Objectable BS.ByteString where + put = lift . DB.put + get = lift DB.get |