Files
Ian Denhardt 76dd8e27ab More optimization.
Mostly optimizing Message.write.

This started off as an attempt to remove some of the cruft in the
allocation routines, get rid of the now-superfluous AppendVec for
segments, and remove the indirection of the MutVar for access to the
segment data (since we never actually resize the segments anymore).

In the course of doing this I discovered we actually *do* still
sometimes resize segments, specifically in the case where a double-far
pointer could be used. This was probably bad for performance, though
I'm not sure how often it came up in practice.

This patch gives us another ~20% speedup for ocap-merkledag.
2020-12-14 17:39:56 -05:00

167 lines
5.7 KiB
Haskell

{- |
Module: Capnp.Pointer
Description: Support for parsing/serializing capnproto pointers
This module provides support for parsing and serializing capnproto pointers.
This is a low-level module; most users will not need to call it directly.
-}
module Capnp.Pointer
( Ptr(..)
, ElementSize(..)
, EltSpec(..)
, parsePtr
, parsePtr'
, serializePtr
, serializePtr'
, parseEltSpec
, serializeEltSpec
)
where
import Data.Bits
import Data.Int
import Data.Word
import Capnp.Bits
-- | A 'Ptr' represents the information in a capnproto pointer.
data Ptr
= StructPtr !Int32 !Word16 !Word16
-- ^ @'StructPtr' off dataSz ptrSz@ is a pointer to a struct
-- at offset @off@ in words from the end of the pointer, with
-- a data section of size @dataSz@ words, and a pointer section
-- of size @ptrSz@ words.
--
-- Note that the value @'StructPtr' 0 0 0@ is illegal, since
-- its encoding is reserved for the "null" pointer.
| ListPtr !Int32 !EltSpec
-- ^ @'ListPtr' off eltSpec@ is a pointer to a list starting at
-- offset @off@ in words from the end of the pointer. @eltSpec@
-- encodes the C and D fields in the encoding spec; see 'EltSpec'
-- for details
| FarPtr !Bool !Word32 !Word32
-- ^ @'FarPtr' twoWords off segment@ is a far pointer, whose landing
-- pad is:
--
-- * two words iff @twoWords@,
-- * @off@ words from the start of the target segment, and
-- * in segment id @segment@.
| CapPtr !Word32
-- ^ @'CapPtr' id@ is a pointer to the capability with the id @id@.
deriving(Show, Eq)
-- | The element size field in a list pointer.
data ElementSize
= Sz0
| Sz1
| Sz8
| Sz16
| Sz32
| Sz64
| SzPtr
deriving(Show, Eq, Enum)
-- | A combination of the C and D fields in a list pointer, i.e. the element
-- size, and either the number of elements in the list, or the total number
-- of /words/ in the list (if size is composite).
data EltSpec
= EltNormal !ElementSize !Word32
-- ^ @'EltNormal' size len@ is a normal (non-composite) element type
-- (C /= 7). @size@ is the size of the elements, and @len@ is the
-- number of elements in the list.
| EltComposite !Int32
-- ^ @EltComposite len@ is a composite element (C == 7). @len@ is the
-- length of the list in words.
deriving(Show, Eq)
-- | @'parsePtr' word@ parses word as a capnproto pointer. A null pointer is
-- parsed as 'Nothing'.
parsePtr :: Word64 -> Maybe Ptr
parsePtr 0 = Nothing
parsePtr p = Just (parsePtr' p)
-- | @'parsePtr'' word@ parses @word@ as a capnproto pointer. It ignores
-- nulls, returning them the same as @(StructPtr 0 0 0)@.
parsePtr' :: Word64 -> Ptr
parsePtr' word =
case bitRange word 0 2 :: Word64 of
0 -> StructPtr
(i30 (lo word))
(bitRange word 32 48)
(bitRange word 48 64)
1 -> ListPtr
(i30 (lo word))
(parseEltSpec word)
2 -> FarPtr
(toEnum (bitRange word 2 3))
(bitRange word 3 32)
(bitRange word 32 64)
3 -> CapPtr (bitRange word 32 64)
_ -> error "unreachable"
-- | @'serializePtr' ptr@ serializes the pointer as a 'Word64', translating
-- 'Nothing' to a null pointer.
--
-- This also changes the offset of zero-sized struct pointers to -1, to avoid
-- them being interpreted as null.
serializePtr :: Maybe Ptr -> Word64
serializePtr Nothing = 0
serializePtr (Just p@(StructPtr (-1) 0 0)) =
serializePtr' p
serializePtr (Just (StructPtr _ 0 0)) =
-- We need to handle this specially, for two reasons.
--
-- First, if the offset is zero, the the normal encoding would be interpreted
-- as null. We can get around this by changing the offset to -1, which will
-- point immediately before the pointer, which is always a valid position --
-- and since the size is zero, we can stick it at any valid position.
--
-- Second, the canonicalization algorithm requires that *all* zero size structs
-- are encoded this way, and doing this for all offsets, rather than only zero
-- offsets, avoids needing extra logic elsewhere.
serializePtr' (StructPtr (-1) 0 0)
serializePtr (Just p) =
serializePtr' p
-- | @'serializePtr'' ptr@ serializes the pointer as a Word64.
--
-- Unlike 'serializePtr', this results in a null pointer on the input
-- @(StructPtr 0 0 0)@, rather than adjusting the offset.
serializePtr' :: Ptr -> Word64
serializePtr' (StructPtr off dataSz ptrSz) =
-- 0 .|.
fromLo (fromI30 off) .|.
(fromIntegral dataSz `shiftL` 32) .|.
(fromIntegral ptrSz `shiftL` 48)
serializePtr' (ListPtr off eltSpec) = -- eltSz numElts) =
1 .|.
fromLo (fromI30 off) .|.
serializeEltSpec eltSpec
serializePtr' (FarPtr twoWords off segId) =
2 .|.
(fromIntegral (fromEnum twoWords) `shiftL` 2) .|.
(fromIntegral off `shiftL` 3) .|.
(fromIntegral segId `shiftL` 32)
serializePtr' (CapPtr index) =
3 .|.
-- (fromIntegral 0 `shiftL` 2) .|.
(fromIntegral index `shiftL` 32)
-- | @'parseEltSpec' word@ reads the 'EltSpec' from @word@, which must be the
-- encoding of a list pointer (this is not verified).
parseEltSpec :: Word64 -> EltSpec
parseEltSpec word = case bitRange word 32 35 of
7 -> EltComposite (i29 (hi word))
sz -> EltNormal (toEnum sz) (bitRange word 35 64)
-- | @'serializeEltSpec' eltSpec@ serializes @eltSpec@ as a 'Word64'. all bits
-- which are not determined by the 'EltSpec' are zero.
serializeEltSpec :: EltSpec -> Word64
serializeEltSpec (EltNormal sz len) =
(fromIntegral (fromEnum sz) `shiftL` 32) .|.
(fromIntegral len `shiftL` 35)
serializeEltSpec (EltComposite words) =
(7 `shiftL` 32) .|.
fromHi (fromI29 words)