packages feed

accelerate-llvm-ptx-1.4.0.0: src/Data/Array/Accelerate/LLVM/PTX/Execute/Async.hs

{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeSynonymInstances       #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- |
-- Module      : Data.Array.Accelerate.LLVM.PTX.Execute.Async
-- Copyright   : [2014..2020] The Accelerate Team
-- License     : BSD3
--
-- Maintainer  : Trevor L. McDonell <trevor.mcdonell@gmail.com>
-- Stability   : experimental
-- Portability : non-portable (GHC extensions)
--

module Data.Array.Accelerate.LLVM.PTX.Execute.Async (

  module Data.Array.Accelerate.LLVM.Execute.Async,
  module Data.Array.Accelerate.LLVM.PTX.Execute.Async,

) where

import Data.Array.Accelerate.Error
import Data.Array.Accelerate.Lifetime

import Data.Array.Accelerate.LLVM.Execute.Async
import Data.Array.Accelerate.LLVM.State

import Data.Array.Accelerate.LLVM.PTX.Target
import Data.Array.Accelerate.LLVM.PTX.Execute.Event                 ( Event )
import Data.Array.Accelerate.LLVM.PTX.Execute.Stream                ( Stream )
import Data.Array.Accelerate.LLVM.PTX.Link.Object                   ( FunctionTable )
import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event       as Event
import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Stream      as Stream

import Control.Monad.Reader
import Data.IORef


-- | Evaluate a parallel computation
--
{-# INLINE evalPar #-}
evalPar :: Par PTX a -> LLVM PTX a
evalPar p = do
  s <- Stream.create
  r <- runReaderT (runPar p) (s, Nothing)
  return r


type ParState = (Stream, Maybe (Lifetime FunctionTable))

ptxStream :: ParState -> Stream
ptxStream = fst

ptxKernel :: ParState -> Maybe (Lifetime FunctionTable)
ptxKernel = snd


-- Implementation
-- --------------

data Future a = Future {-# UNPACK #-} !(IORef (IVar a))

data IVar a
    = Full !a
    | Pending {-# UNPACK #-} !Event !(IO ()) !a
    | Empty !(IO ())


askParState :: Par PTX ParState
askParState = Par ask

asksParState :: (ParState -> a) -> Par PTX a
asksParState f = Par (asks f)

localParState :: (ParState -> ParState) -> Par PTX a -> Par PTX a
localParState f (Par m) = Par (local f m)

instance MonadReader PTX (Par PTX) where
  ask = Par (lift ask)
  local f (Par (ReaderT g)) = Par (ReaderT (\parstate -> local f (g parstate)))

instance Async PTX where
  type FutureR PTX = Future

  newtype Par PTX a = Par { runPar :: ReaderT ParState (LLVM PTX) a }
    deriving ( Functor, Applicative, Monad, MonadIO )

  {-# INLINEABLE new     #-}
  {-# INLINEABLE newFull #-}
  new       = Future <$> liftIO (newIORef (Empty (return ())))
  newFull v = Future <$> liftIO (newIORef (Full v))

  {-# INLINEABLE spawn #-}
  spawn m = do
    s' <- liftPar Stream.create
    r  <- localParState (const (s', Nothing)) m
    liftIO (Stream.destroy s')
    return r

  {-# INLINEABLE fork #-}
  fork m = do
    s' <- liftPar (Stream.create)
    () <- localParState (const (s', Nothing)) m
    liftIO (Stream.destroy s')

  -- When we call 'put' the actual work may not have been evaluated yet; get
  -- a new event in the current execution stream and once that is filled we can
  -- transition the IVar to Full.
  --
  {-# INLINEABLE put #-}
  put (Future ref) v = do
    stream <- asksParState ptxStream
    kernel <- asksParState ptxKernel
    event  <- liftPar (Event.waypoint stream)
    ready  <- liftIO  (Event.query event)
    let cleanupK = case kernel of
                     Just k -> touchLifetime k
                     Nothing -> return ()
    liftIO . atomicModifyIORef' ref $ \case
      Empty cleanup -> if ready then (Full v, ())
                                else (Pending event (cleanup >> cleanupK) v, ())
      _     -> internalError "multiple put"

  -- Get the value of Future. Since the actual cross-stream synchronisation
  -- happens on the device, we should never have to block/reschedule the main
  -- thread waiting on a value; if we get an empty IVar at this point, something
  -- has gone wrong.
  --
  {-# INLINEABLE get #-}
  get (Future ref) = do
    stream <- asksParState ptxStream
    liftIO  $ do
      ivar <- readIORef ref
      case ivar of
        Full v            -> return v
        Pending event cleanup v -> do
          ready <- Event.query event
          if ready
            then do
              writeIORef ref (Full v)
              cleanup
            else
              Event.after event stream
          return v
        Empty _         -> internalError "blocked on an IVar"

  {-# INLINEABLE block #-}
  block = liftIO . wait

  {-# INLINE liftPar #-}
  liftPar = Par . lift


-- | Block the calling _host_ thread until the value offered by the future is
-- available.
--
{-# INLINEABLE wait #-}
wait :: Future a -> IO a
wait (Future ref) = do
  ivar <- readIORef ref
  case ivar of
    Full v            -> return v
    Pending event cleanup v -> do
      Event.block event
      writeIORef ref (Full v)
      cleanup
      return v
    Empty _         -> internalError "blocked on an IVar"

{-# INLINEABLE putCleanup #-}
putCleanup :: HasCallStack => FutureR PTX a -> IO () -> a -> Par PTX ()
putCleanup (Future ref) cleanup v = do
  stream <- asksParState ptxStream
  kernel <- asksParState ptxKernel
  event  <- liftPar (Event.waypoint stream)
  ready  <- liftIO  (Event.query event)
  let cleanupK = case kernel of
                   Just k -> touchLifetime k
                   Nothing -> return ()
  liftIO . atomicModifyIORef' ref $ \case
    Empty cleanup2 -> if ready then (Full v, ())
                               else (Pending event (cleanup2 >> cleanup >> cleanupK) v, ())
    _     -> internalError "multiple put"

{-# INLINEABLE addCleanup #-}
addCleanup :: HasCallStack => FutureR PTX a -> IO () -> Par PTX ()
addCleanup (Future ref) cleanup = liftIO $ do
  toRunNow <- atomicModifyIORef' ref $ \case
    Full v -> (Full v, cleanup)
    Pending event cleanup2 v -> (Pending event (cleanup2 >> cleanup) v, return ())
    Empty cleanup2 -> (Empty (cleanup2 >> cleanup), return ())
  toRunNow