packages feed

posix-api-0.7.2.0: src/Linux/Epoll/Types.hsc

{-# language BangPatterns #-}
{-# language BinaryLiterals #-}
{-# language DataKinds #-}
{-# language DerivingStrategies #-}
{-# language DuplicateRecordFields #-}
{-# language GADTSyntax #-}
{-# language GeneralizedNewtypeDeriving #-}
{-# language KindSignatures #-}
{-# language MagicHash #-}
{-# language NamedFieldPuns #-}
{-# language PolyKinds #-}
{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}
{-# language DataKinds #-}
{-# language UnboxedTuples #-}

-- This is needed because hsc2hs does not currently handle ticked
-- promoted data constructors correctly.
{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}

#define _GNU_SOURCE
#include <sys/epoll.h>
#include "custom.h"

-- | All of the data constructors provided by this module are unsafe.
--   Only use them if you really know what you are doing.
module Linux.Epoll.Types
  ( EpollFlags(..)
  , ControlOperation(..)
  , Exchange(..)
  , Events(..)
  , Event(..)
  , PrimEpollData(..)
    -- * Flags
  , closeOnExec
  , add
  , modify
  , delete
    -- * Events
  , input
  , output
  , priority
  , hangup
  , readHangup
  , error
  , edgeTriggered
    -- * Events Combinators
  , containsAnyEvents
  , containsAllEvents
    -- * Marshalling
  , sizeofEvent
  , peekEventEvents
  , peekEventDataFd
  , peekEventDataPtr
  , peekEventDataU32
  , peekEventDataU64
  , pokeEventDataU64
  -- , readEventDataU64
  -- , writeEventDataU64
  ) where

import Prelude hiding (truncate,error)

import Data.Bits (Bits,(.&.),(.|.),unsafeShiftL,unsafeShiftR)
import Data.Kind (Type)
import Data.Primitive.Addr (Addr(..))
import Data.Primitive (Prim)
import Data.Primitive (indexByteArray##,writeByteArray##,readByteArray##)
import Data.Primitive (indexOffAddr##,readOffAddr##,writeOffAddr##)
import Data.Word (Word32,Word64)
import Foreign.C.Types (CInt(..))
import Foreign.Storable (Storable,peekByteOff,pokeByteOff)
import GHC.Exts (Int(I##),(+##),(*##))
import GHC.Exts (State##,Int##,Addr##,MutableByteArray##,ByteArray##)
import GHC.Ptr (Ptr(..))
import Posix.Poll (Exchange(..))
import System.Posix.Types (Fd(..))

import qualified Data.Primitive as PM

newtype ControlOperation = ControlOperation CInt
  deriving stock (Eq)

newtype EpollFlags = EpollFlags CInt
  deriving stock (Eq)
  deriving newtype (Bits)

instance Semigroup EpollFlags where (<>) = (.|.)
instance Monoid EpollFlags where mempty = EpollFlags 0

newtype Events :: Exchange -> Type where
  Events :: Word32 -> Events e
  deriving stock (Eq)
  deriving newtype (Bits,Storable,Prim)

instance Semigroup (Events e) where (<>) = (.|.)
instance Monoid (Events e) where mempty = Events 0

-- | A data type corresponding to @struct epoll_event@. Linux
-- defines this as:
--
-- > typedef union epoll_data {
-- >     void    *ptr;
-- >     int      fd;
-- >     uint32_t u32;
-- >     uint64_t u64;
-- > } epoll_data_t;
-- >
-- > struct epoll_event {
-- >     uint32_t     events; /* Epoll events */
-- >     epoll_data_t data;   /* User data variable */
-- > };
--
-- It is a little difficult to capture what this type conveys, but
-- we make an attempt. The second argument to the @Event@ type
-- constructor is either @Addr@, @Fd@, @Word32@, or @Word64@. This
-- corresponds to the four possibilities in the @epoll_data@ union
-- type. As long as the user monomorphizes this type when using
-- it, there should not be any performance penalty for the
-- flexibility afforded by this approach.
data Event :: Exchange -> Type -> Type where
  Event ::
    { events :: !(Events e)
      -- ^ Epoll events
    , payload :: !a
      -- ^ User data variable, named @data@ in @struct epoll_event@.
    } -> Event e a

class PrimEpollData a where
  indexByteArrayEpoll :: ByteArray## -> Int## -> Event e a
  readByteArrayEpoll :: MutableByteArray## s -> Int## -> State## s -> (## State## s, Event e a ##)
  writeByteArrayEpoll :: MutableByteArray## s -> Int## -> Event e a -> State## s -> State## s
  indexOffAddrEpoll :: Addr## -> Int## -> Event e a
  readOffAddrEpoll :: Addr## -> Int## -> State## s -> (## State## s, Event e a ##)
  writeOffAddrEpoll :: Addr## -> Int## -> Event e a -> State## s -> State## s

instance PrimEpollData a => Prim (Event e a) where
  {-# inline sizeOf# #-}
  {-# inline alignment# #-}
  {-# inline indexByteArray# #-}
  {-# inline readByteArray# #-}
  {-# inline writeByteArray# #-}
  {-# inline setByteArray# #-}
  {-# inline indexOffAddr# #-}
  {-# inline readOffAddr# #-}
  {-# inline writeOffAddr# #-}
  {-# inline setOffAddr# #-}
  sizeOf## _ = unI #{size struct epoll_event}
  alignment## _ = PM.alignment## (undefined :: Word32)
  indexByteArray## = indexByteArrayEpoll
  readByteArray## = readByteArrayEpoll
  writeByteArray## = writeByteArrayEpoll
  setByteArray## = PM.defaultSetByteArray##
  indexOffAddr## = indexOffAddrEpoll
  readOffAddr## = readOffAddrEpoll
  writeOffAddr## = writeOffAddrEpoll
  setOffAddr## = PM.defaultSetOffAddr##

instance PrimEpollData Fd where
  {-# inline indexByteArrayEpoll #-}
  {-# inline readByteArrayEpoll #-}
  {-# inline writeByteArrayEpoll #-}
  {-# inline indexOffAddrEpoll #-}
  {-# inline readOffAddrEpoll #-}
  {-# inline writeOffAddrEpoll #-}
  indexByteArrayEpoll arr i = Event
    { events = #{indexByteArrayHash struct epoll_event, events} arr i
    , payload = #{indexByteArrayHash struct epoll_event, data.fd} arr i
    }
  writeByteArrayEpoll arr i Event{events,payload} s0 =
    case #{writeByteArrayHash struct epoll_event, events} arr i events s0 of
      s1 -> #{writeByteArrayHash struct epoll_event, data.fd} arr i payload s1
  readByteArrayEpoll arr i s0 =
    case #{readByteArrayHash struct epoll_event, events} arr i s0 of
      (## s1, events ##) -> case #{readByteArrayHash struct epoll_event, data.fd} arr i s1 of
        (## s2, payload ##) -> (## s2, Event{events,payload} ##)
  indexOffAddrEpoll arr i = Event
    { events = #{indexOffAddrHash struct epoll_event, events} arr i
    , payload = #{indexOffAddrHash struct epoll_event, data.fd} arr i
    }
  writeOffAddrEpoll arr i Event{events,payload} s0 =
    case #{writeOffAddrHash struct epoll_event, events} arr i events s0 of
      s1 -> #{writeOffAddrHash struct epoll_event, data.fd} arr i payload s1
  readOffAddrEpoll arr i s0 =
    case #{readOffAddrHash struct epoll_event, events} arr i s0 of
      (## s1, events ##) -> case #{readOffAddrHash struct epoll_event, data.fd} arr i s1 of
        (## s2, payload ##) -> (## s2, Event{events,payload} ##)

-- | Since @epoll_event@ includes an unaligned 64-bit word, it is
-- difficult to use @hsc2hs@ to generate the marshalling code. Consequently,
-- the offsets of @events@ and @data@ are currently hardcoded. Open an
-- issue in this causes a problem on your platform.
instance PrimEpollData Word64 where
  {-# inline indexByteArrayEpoll #-}
  {-# inline readByteArrayEpoll #-}
  {-# inline writeByteArrayEpoll #-}
  {-# inline indexOffAddrEpoll #-}
  {-# inline readOffAddrEpoll #-}
  {-# inline writeOffAddrEpoll #-}
  indexByteArrayEpoll arr i = Event
    { events = PM.indexByteArray## arr (i *## 3##)
    , payload = composePayload
        (PM.indexByteArray## arr ((i *## 3##) +# 1##))
        (PM.indexByteArray## arr ((i *## 3##) +# 2##))
    }
  writeByteArrayEpoll arr i Event{events,payload} s0 = case PM.writeByteArray## arr (i *## 3##) events s0 of
    s1 -> case PM.writeByteArray## arr ((i *## 3##) +## 1##) pa s1 of
      s2 -> PM.writeByteArray## arr ((i *## 3##) +## 2##) pb s2
    where
    !(pa,pb) = decomposePayload payload
  readByteArrayEpoll arr i s0 = case PM.readByteArray## arr (i *## 3##) s0 of
    (## s1, events ##) -> case PM.readByteArray## arr ((i *## 3##) +## 1##) s1 of
      (## s2, pa ##) -> case PM.readByteArray## arr ((i *## 3##) +## 2##) s2 of
        (## s3, pb ##) -> let payload = composePayload pa pb in
          (## s3, Event{events,payload} ##)
  indexOffAddrEpoll arr i = Event
    { events = PM.indexOffAddr## arr (i *## 3##)
    , payload = composePayload
        (PM.indexOffAddr## arr ((i *## 3##) +## 1##))
        (PM.indexOffAddr## arr ((i *## 3##) +## 2##))
    }
  writeOffAddrEpoll arr i Event{events,payload} s0 = case PM.writeOffAddr## arr (i *## 3##) events s0 of
    s1 -> case PM.writeOffAddr## arr ((i *## 3##) +## 1##) pa s1 of
      s2 -> PM.writeOffAddr## arr ((i *## 3##) +## 2##) pb s2
    where
    !(pa,pb) = decomposePayload payload
  readOffAddrEpoll arr i s0 = case PM.readOffAddr## arr (i *## 3##) s0 of
    (## s1, events ##) -> case PM.readOffAddr## arr ((i *## 3##) +## 1##) s1 of
      (## s2, pa ##) -> case PM.readOffAddr## arr ((i *## 3##) +## 2##) s2 of
        (## s3, pb ##) -> let payload = composePayload pa pb in
          (## s3, Event{events,payload} ##)

-- | The @EPOLL_CTL_ADD@ control operation.
add :: ControlOperation
add = ControlOperation #{const EPOLL_CTL_ADD}

-- | The @EPOLL_CTL_MOD@ control operation.
modify :: ControlOperation
modify = ControlOperation #{const EPOLL_CTL_MOD}

-- | The @EPOLL_CTL_DEL@ control operation.
delete :: ControlOperation
delete = ControlOperation #{const EPOLL_CTL_DEL}

-- | The @EPOLL_CLOEXEC@ flag.
closeOnExec :: EpollFlags
closeOnExec = EpollFlags #{const EPOLL_CLOEXEC}

-- | The @EPOLLIN@ event. Can appear in a request or a response.
input :: Events e
input = Events #{const EPOLLIN}

-- | The @EPOLLOUT@ event. Can appear in a request or a response.
output :: Events e
output = Events #{const EPOLLOUT}

-- | The @EPOLLPRI@ event. Can appear in a request or a response.
priority :: Events e
priority = Events #{const EPOLLPRI}

-- | The @EPOLLERR@ event. The
-- <http://man7.org/linux/man-pages/man2/epoll_ctl.2.html epoll_ctl documentation> says
-- "@epoll_wait@ will always wait for this event; it is not necessary to set it in @events@".
-- Consequently, in this library, it has been marked as only appearing in @Response@ positions.
error :: Events Response
error = Events #{const EPOLLERR}

-- | The @EPOLLHUP@ event. The
-- <http://man7.org/linux/man-pages/man2/epoll_ctl.2.html epoll_ctl documentation> says
-- "@epoll_wait@ will always wait for this event; it is not necessary to set it in @events@".
-- Consequently, in this library, it has been marked as only appearing in @Response@ positions.
hangup :: Events Response
hangup = Events #{const EPOLLHUP}

-- | The @EPOLLRDHUP@ event. Can appear in a request or a response.
readHangup :: Events e
readHangup = Events #{const EPOLLRDHUP}

-- | The @EPOLLET@ event. Only appears in requests.
edgeTriggered :: Events Request
edgeTriggered = Events #{const EPOLLET}

-- | Does the first event set entirely contain the second one? That is,
-- is the second argument a subset of the first?
containsAllEvents :: Events e -> Events e -> Bool
containsAllEvents (Events a) (Events b) = a .&. b == b

-- | Does the first event set contain any of the events from the second one?
containsAnyEvents :: Events e -> Events e -> Bool
containsAnyEvents (Events a) (Events b) = (a .&. b) /= 0

sizeofEvent :: Int
sizeofEvent = #{size struct epoll_event}

-- | Read @events@ from @struct epoll_event@.
peekEventEvents :: Addr -> IO (Events e)
peekEventEvents (Addr p) = #{peek struct epoll_event, events} (Ptr p)

-- | Read @data.fd@ from @struct epoll_event@.
peekEventDataFd :: Addr -> IO Fd
peekEventDataFd (Addr p) = #{peek struct epoll_event, data.fd} (Ptr p)

-- | Read @data.ptr@ from @struct epoll_event@.
peekEventDataPtr :: Addr -> IO Addr
peekEventDataPtr (Addr p) = do
  Ptr q <- #{peek struct epoll_event, data.ptr} (Ptr p)
  pure (Addr q)

-- | Read @data.u32@ from @struct epoll_event@.
peekEventDataU32 :: Addr -> IO Word32
peekEventDataU32 (Addr p) = #{peek struct epoll_event, data.u32} (Ptr p)

-- | Read @data.u64@ from @struct epoll_event@.
peekEventDataU64 :: Addr -> IO Word64
peekEventDataU64 (Addr p) = #{peek struct epoll_event, data.u64} (Ptr p)

-- | Write @data.u64@ from @struct epoll_event@.
pokeEventDataU64 :: Addr -> Word64 -> IO ()
pokeEventDataU64 (Addr p) w = #{poke struct epoll_event, data.u64} (Ptr p) w

composePayload :: Word32 -> Word32 -> Word64
{-# inline composePayload #-}
composePayload a b = unsafeShiftL (word32ToWord64 a) 32 .|. word32ToWord64 b

decomposePayload :: Word64 -> (Word32,Word32)
{-# inline decomposePayload #-}
decomposePayload w = (word64ToWord32 (unsafeShiftR w 32), word64ToWord32 w)

word32ToWord64 :: Word32 -> Word64
word32ToWord64 = fromIntegral

word64ToWord32 :: Word64 -> Word32
word64ToWord32 = fromIntegral

unI :: Int -> Int##
unI (I## i) = i