packages feed

fir-0.1.0.0: src/CodeGen/Atomics.hs

{-# LANGUAGE BlockArguments      #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE TupleSections       #-}

{-|
Module: CodeGen.Atomics

Code generation for atomic operations.
-}

module CodeGen.Atomics
  ( atomicInstruction )
  where

-- base
import Control.Monad
  ( unless )

-- mtl
import Control.Monad.Except
  ( throwError )

-- lens
import Control.Lens
  ( view )

-- text-short
import Data.Text.Short
  ( ShortText )
import qualified Data.Text.Short as ShortText
  ( pack )

-- fir
import CodeGen.Binary
  ( instruction )
import CodeGen.IDs
  ( laidOutTypeID, constID )
import CodeGen.Instruction
  ( Args, toArgs
  , ID(ID), Instruction(..)
  , Pointer(..)
  )
import CodeGen.Monad
  ( CGMonad
  , MonadFresh(fresh)
  )
import CodeGen.Pointers
  ( relayout )
import CodeGen.State
  ( _backend
  , requireCapability, requireExtension
  )
import FIR.Prim.Atomic
  ( SAtomicOp(..), atomicOp, SAtomicShape(..), atomicShape )
import qualified SPIRV.Capability      as SPIRV
import qualified SPIRV.Extension       as SPIRV.Extension
import qualified SPIRV.Layout          as SPIRV
import qualified SPIRV.Operation       as SPIRV.Op
import qualified SPIRV.PrimTy          as SPIRV
import qualified SPIRV.ScalarTy        as SPIRV
import qualified SPIRV.Stage           as SPIRV
import qualified SPIRV.Storage         as Storage
import qualified SPIRV.Synchronisation as SPIRV

----------------------------------------------------------------------------

-- | Perform an atomic operation on the scalar a pointer points to,
-- with the given operands.
atomicInstruction
  :: SAtomicOp op -> Pointer -> [ ID ] -> CGMonad (ID, SPIRV.PrimTy)
atomicInstruction sOp (Pointer ptrID (SPIRV.PointerTy storage lay ty)) operandIDs
  = do
      bk <- view _backend
      unless ( storage `elem` atomicStorageClasses bk ) $
        throwError
          (  "codeGen: atomic operations are unsupported on pointers of storage "
          <> "class " <> ShortText.pack ( show storage ) <> " for backend "
          <> ShortText.pack ( show bk ) <> ".\n\
             \Supported storage classes: "
          <> ShortText.pack ( show ( atomicStorageClasses bk ) ) <> "."
          )
      op <- case ty of
        SPIRV.Scalar sTy -> atomicOperation sOp sTy
        _ -> throwError
               (  "codeGen: " <> opName <> " is unsupported on values of type "
               <> ShortText.pack ( show ty ) <> "."
               )
      -- Device scope and relaxed memory semantics, as for GLSL atomics.
      scopeID         <- constID ( SPIRV.synchronisationScope SPIRV.Device )
      semanticsID     <- constID ( SPIRV.memorySemanticsBitmask relaxed )
      laidOutOperands <-
        traverse ( \ v -> relayout SPIRV.logical lay (v, ty) ) operandIDs
      let
        args :: Args
        args = case ( atomicShape sOp, laidOutOperands ) of
          -- A compare-exchange has separate memory semantics for success and
          -- failure, and takes the new value before the expected value.
          ( SCompares, [ expectedID, newID ] )
            -> toArgs
                 [ ptrID, scopeID, semanticsID, semanticsID, newID, expectedID ]
          _ -> toArgs ( ptrID : scopeID : semanticsID : laidOutOperands )
      case atomicShape sOp of
        SWrites -> do
          instruction
            Instruction
              { operation = op
              , resTy = Nothing
              , resID = Nothing
              , args
              }
          pure (ID 0, SPIRV.Unit) -- ID should never be used
        _ -> do
          tyID <- laidOutTypeID lay ty
          v <- fresh
          instruction
            Instruction
              { operation = op
              , resTy = Just tyID
              , resID = Just v
              , args
              }
          (, ty) <$> relayout lay SPIRV.logical (v, ty)
  where
    opName :: ShortText
    opName = ShortText.pack ( show ( atomicOp sOp ) )
    relaxed :: SPIRV.MemorySemantics
    relaxed = SPIRV.MemorySemantics ( SPIRV.Lock [] ) []

-- | The SPIR-V instruction for an atomic operation on scalars of the given
-- type, requiring the capabilities and extensions it needs.
atomicOperation :: SAtomicOp op -> SPIRV.ScalarTy -> CGMonad SPIRV.Op.Operation
atomicOperation sOp sTy = case sTy of
  SPIRV.Integer sgn w -> do
    case w of
      SPIRV.W32 -> pure ()
      SPIRV.W64 -> requireCapability SPIRV.Int64Atomics
      _         -> unsupported
    pure case sOp of
      SAtomicLoad            -> SPIRV.Op.AtomicLoad
      SAtomicStore           -> SPIRV.Op.AtomicStore
      SAtomicExchange        -> SPIRV.Op.AtomicExchange
      SAtomicCompareExchange -> SPIRV.Op.AtomicCompareExchange
      SAtomicIncrement       -> SPIRV.Op.AtomicIIncrement
      SAtomicDecrement       -> SPIRV.Op.AtomicIDecrement
      SAtomicAdd             -> SPIRV.Op.AtomicIAdd
      SAtomicSub             -> SPIRV.Op.AtomicISub
      SAtomicMin             -> case sgn of
        SPIRV.Signed   -> SPIRV.Op.AtomicSMin
        SPIRV.Unsigned -> SPIRV.Op.AtomicUMin
      SAtomicMax             -> case sgn of
        SPIRV.Signed   -> SPIRV.Op.AtomicSMax
        SPIRV.Unsigned -> SPIRV.Op.AtomicUMax
      SAtomicAnd             -> SPIRV.Op.AtomicAnd
      SAtomicOr              -> SPIRV.Op.AtomicOr
      SAtomicXor             -> SPIRV.Op.AtomicXor
  SPIRV.Floating w -> case sOp of
    SAtomicLoad     -> pure SPIRV.Op.AtomicLoad
    SAtomicStore    -> pure SPIRV.Op.AtomicStore
    SAtomicExchange -> pure SPIRV.Op.AtomicExchange
    SAtomicAdd      -> do
      case w of
        SPIRV.W16 -> do
          requireCapability SPIRV.AtomicFloat16AddEXT
          requireExtension  SPIRV.Extension.SPV_EXT_shader_atomic_float16_add
        SPIRV.W32 -> do
          requireCapability SPIRV.AtomicFloat32AddEXT
          requireExtension  SPIRV.Extension.SPV_EXT_shader_atomic_float_add
        SPIRV.W64 -> do
          requireCapability SPIRV.AtomicFloat64AddEXT
          requireExtension  SPIRV.Extension.SPV_EXT_shader_atomic_float_add
        SPIRV.W8 -> unsupported
      pure SPIRV.Op.AtomicFAddEXT
    SAtomicMin -> SPIRV.Op.AtomicFMinEXT <$ floatMinMax
    SAtomicMax -> SPIRV.Op.AtomicFMaxEXT <$ floatMinMax
    _ -> unsupported
    where
      floatMinMax :: CGMonad ()
      floatMinMax = do
        case w of
          SPIRV.W16 -> requireCapability SPIRV.AtomicFloat16MinMaxEXT
          SPIRV.W32 -> requireCapability SPIRV.AtomicFloat32MinMaxEXT
          SPIRV.W64 -> requireCapability SPIRV.AtomicFloat64MinMaxEXT
          SPIRV.W8  -> unsupported
        requireExtension SPIRV.Extension.SPV_EXT_shader_atomic_float_min_max
  where
    unsupported :: CGMonad a
    unsupported = throwError
      (  "codeGen: " <> ShortText.pack ( show ( atomicOp sOp ) )
      <> " is unsupported on values of type "
      <> ShortText.pack ( show sTy ) <> "."
      )

-- | Storage classes of the pointers atomic operations accept, as specified by
-- the SPIR-V execution environment of each backend.
atomicStorageClasses :: SPIRV.Backend -> [ Storage.StorageClass ]
atomicStorageClasses SPIRV.Vulkan
  = [ Storage.Uniform, Storage.Workgroup, Storage.Image, Storage.StorageBuffer ]
atomicStorageClasses SPIRV.OpenCL
  = [ Storage.Function, Storage.Workgroup
    , Storage.CrossWorkgroup, Storage.Generic
    ]