urbit/pkg/hs-urbit/lib/Data/Noun/Jam/Put.hs
2019-06-29 20:05:45 -07:00

288 lines
7.3 KiB
Haskell

{-# LANGUAGE MagicHash #-}
module Data.Noun.Jam.Put where
import ClassyPrelude
import GHC.Prim
import GHC.Natural
import GHC.Integer.GMP.Internals
import Control.Lens (view)
import Control.Monad (guard)
import Data.Bits (shiftL, shiftR, setBit, clearBit, (.|.), (.&.))
import Data.Map (Map)
import Data.Noun.Atom (Atom(MkAtom), wordBitWidth#)
import Data.Noun.Atom (toAtom, takeBits, bitWidth)
import Data.Noun (Noun(Atom, Cell))
import Data.Noun.Pill (bigNatWords)
import Data.Vector.Primitive ((!))
import Foreign.Marshal.Alloc (mallocBytes, free)
import Foreign.Ptr (Ptr, castPtr, plusPtr, ptrToWordPtr)
import Foreign.Storable (peek, poke)
import GHC.Int (Int(I#))
import GHC.Word (Word(W#))
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.ByteString.Unsafe as BS
import qualified Data.HashTable.IO as H
import qualified Data.Vector.Primitive as VP
-- Types -----------------------------------------------------------------------
{-|
The encoder state.
- ptr: Pointer into the output buffer.
- reg: Next 64 bits of output, partially written.
- off: Number of bits already written into `reg`
- pos: Total number of bits written.
-}
data S = S
{ ptr :: {-# UNPACK #-} !(Ptr Word)
, reg :: {-# UNPACK #-} !Word
, off :: {-# UNPACK #-} !Int
, pos :: {-# UNPACK #-} !Word
} deriving (Show,Eq,Ord)
data PutResult a = PutResult {-# UNPACK #-} !S !a
deriving Functor
newtype Put a = Put
{ runPut :: H.LinearHashTable Noun Word
-> S
-> IO (PutResult a)
}
--------------------------------------------------------------------------------
{-# INLINE insRef #-}
insRef :: Noun -> Word -> Put ()
insRef n w = Put \tbl s -> PutResult s <$> H.insert tbl n w
{-# INLINE getRef #-}
getRef :: Noun -> Put (Maybe Word)
getRef n = Put \tbl s -> PutResult s <$> H.lookup tbl n
{-
1. Write the register to the output, and increment the output pointer.
-}
{-# INLINE flush #-}
flush :: Put ()
flush = Put $ \tbl s@S{..} -> do
poke ptr reg
pure $ PutResult (s { ptr = ptr `plusPtr` 8 }) ()
{-# INLINE update #-}
update :: (S -> S) -> Put ()
update f = Put \tbl s@S{..} -> pure (PutResult (f s) ())
{-# INLINE setRegOff #-}
setRegOff :: Word -> Int -> Put ()
setRegOff r o = update \s@S{..} -> (s {reg=r, off=o})
{-# INLINE setReg #-}
setReg :: Word -> Put ()
setReg r = update \s@S{..} -> (s { reg=r })
{-# INLINE getS #-}
getS :: Put S
getS = Put $ \tbl s -> pure (PutResult s s)
{-# INLINE putS #-}
putS :: S -> Put ()
putS s = Put $ \tbl _ -> pure (PutResult s ())
{-
To write a bit:
| reg |= 1 << regI
| regI <- (regI + 1) % 64
| if (!regI):
| buf[w++] <- reg
| reg <- 0
-}
{-# INLINE writeBit #-}
writeBit :: Bool -> Put ()
writeBit b = Put $ \tbl s@S{..} -> do
let s' = s { reg = (if b then setBit else clearBit) reg off
, off = (off + 1) `mod` 64
, pos = pos + 1
}
if off == 63
then runPut (flush >> setRegOff 0 0) tbl s'
else pure $ PutResult s' ()
{-
To write a 64bit word:
| reg |= w << regI
| buf[bufI++] = reg
| reg = w >> (64 - regI)
-}
{-# INLINE writeWord #-}
writeWord :: Word -> Put ()
writeWord wor = do
S{..} <- getS
setReg (reg .|. shiftL wor off)
flush
setReg (shiftR wor (64 - off))
{-
To write some bits (< 64) from a word:
| reg |= wor << regI
| regI += wid
|
| if (regI >= 64)
| regI -= 64
| buf[w] = x
| reg = wor >> (wid - regI)
-}
{-# INLINE writeBitsFromWord #-}
writeBitsFromWord :: Int -> Word -> Put ()
writeBitsFromWord wid wor = do
s <- getS
let s' = s { reg = reg s .|. shiftL wor (off s)
, off = off s + wid
}
if (off s' < 64)
then do putS s'
else do update (\s -> s { off = off s - 64 })
flush
setReg (shiftR wor (wid - off s'))
{-
Write all of the the signficant bits of a direct atom.
-}
{-# INLINE writeAtomWord# #-}
writeAtomWord# :: Word# -> Put ()
writeAtomWord# w = writeBitsFromWord (I# (word2Int# (wordBitWidth# w))) (W# w)
{-# INLINE writeAtomWord #-}
writeAtomWord :: Word -> Put ()
writeAtomWord (W# w) = writeAtomWord# w
{-
Write all of the the signficant bits of an indirect atom.
TODO Use memcpy when the bit-offset of the output is divisible by 8.
-}
{-# INLINE writeAtomBigNat #-}
writeAtomBigNat :: BigNat -> Put ()
writeAtomBigNat (view bigNatWords -> words) = do
let lastIdx = VP.length words - 1
for_ [0..(lastIdx-1)] \i ->
writeWord (words ! i)
writeAtomWord (words ! lastIdx)
{-# INLINE writeAtomBits #-}
writeAtomBits :: Atom -> Put ()
writeAtomBits = \case MkAtom (NatS# wd) -> writeAtomWord# wd
MkAtom (NatJ# bn) -> writeAtomBigNat bn
-- Put Instances ---------------------------------------------------------------
instance Functor Put where
fmap f g = Put $ \tbl s -> do
PutResult s' a <- runPut g tbl s
pure $ PutResult s' (f a)
{-# INLINE fmap #-}
instance Applicative Put where
pure x = Put (\_ s -> return $ PutResult s x)
{-# INLINE pure #-}
Put f <*> Put g = Put $ \tbl s1 -> do
PutResult s2 f' <- f tbl s1
PutResult s3 g' <- g tbl s2
return $ PutResult s3 (f' g')
{-# INLINE (<*>) #-}
Put f *> Put g = Put $ \tbl s1 -> do
PutResult s2 _ <- f tbl s1
g tbl s2
{-# INLINE (*>) #-}
instance Monad Put where
return = pure
{-# INLINE return #-}
(>>) = (*>)
{-# INLINE (>>) #-}
Put x >>= f = Put $ \tbl s -> do
PutResult s' x' <- x tbl s
runPut (f x') tbl s'
{-# INLINE (>>=) #-}
--------------------------------------------------------------------------------
doPut :: Word64 -> Put () -> ByteString
doPut sz m =
unsafePerformIO $ do
tbl <- H.new
buf <- mallocBytes (fromIntegral $ wordSz*8)
_ <- runPut m tbl (S buf 0 0 0)
BS.unsafePackCStringFinalizer (castPtr buf) byteSz (free buf)
where
wordSz = fromIntegral (sz `divUp` 64)
byteSz = fromIntegral (sz `divUp` 8)
divUp x y = (x `div` y) + (if x `mod` y == 0 then 0 else 1)
--------------------------------------------------------------------------------
{-
TODO Handle back references
-}
writeNoun :: Noun -> Put ()
writeNoun n = do
p <- pos <$> getS
mRef <- getRef n
case (mRef, n) of
(Nothing, Atom a) -> writeAtom a
(Nothing, Cell h t) -> writeCell h t
(Just bk, Atom a) | a < toAtom bk -> writeAtom a
(Just bk, _) -> writeBackRef bk
insRef n p
{-# INLINE writeMat #-}
writeMat :: Atom -> Put ()
writeMat atm = do
writeBitsFromWord (preWid+1) (shiftL (1 :: Word) preWid)
writeAtomBits extras
writeAtomBits atm
where
atmWid = bitWidth atm :: Atom
preWid = bitWidth atmWid :: Int
prefix = shiftL (1 :: Word) (fromIntegral preWid)
extras = takeBits (preWid-1) (toAtom atmWid)
{-# INLINE writeCell #-}
writeCell :: Noun -> Noun -> Put ()
writeCell h t = do
writeBit True
writeBit False
writeNoun h
writeNoun t
{-# INLINE writeAtom #-}
writeAtom :: Atom -> Put ()
writeAtom a = do
writeBit False
writeMat a
{-# INLINE writeBackRef #-}
writeBackRef :: Word -> Put ()
writeBackRef a = do
writeBit True
writeBit True
writeMat (toAtom a)